Чтобы понять разницу между полиномиальным и псевдополиномиальным временем, нам нужно начать с формализации того, что означает «полиномиальное время».
Обычная интуиция для полиномиального времени - это «время O (n k ) для некоторого k». Например, сортировка выбора выполняется за время O (n 2 ), которое является полиномиальным временем, в то время как решение TSP методом грубой силы занимает время O (n · n!), Что не является полиномиальным временем.
Все эти среды выполнения относятся к некоторой переменной n, которая отслеживает размер ввода. Например, при сортировке по выбору n обозначает количество элементов в массиве, а в TSP n обозначает количество узлов в графе. Чтобы стандартизировать определение того, что на самом деле означает «n» в этом контексте, формальное определение временной сложности определяет «размер» проблемы следующим образом:
Размер входных данных проблемы - это количество битов, необходимых для записи этих входных данных.
Например, если входными данными для алгоритма сортировки является массив 32-битных целых чисел, то размер входных данных будет 32n, где n - количество записей в массиве. В графе с n узлами и m ребрами вход может быть определен как список всех узлов, за которым следует список всех ребер, для чего потребуется Ω (n + m) бит.
Учитывая это определение, формальное определение полиномиального времени следующее:
Алгоритм выполняется за полиномиальное время, если время его выполнения составляет O (x k ) для некоторой константы k, где x обозначает количество битов входных данных, переданных алгоритму.
При работе с алгоритмами, обрабатывающими графы, списки, деревья и т. Д., Это определение более или менее согласуется с обычным определением. Например, предположим, что у вас есть алгоритм сортировки, который сортирует массивы 32-битных целых чисел. Если для этого вы используете что-то вроде сортировки по выбору, время выполнения в зависимости от количества входных элементов в массиве будет O (n 2 ). Но как n, количество элементов во входном массиве, соответствует количеству битов ввода? Как упоминалось ранее, количество битов ввода будет x = 32n. Следовательно, если мы выразим время выполнения алгоритма через x, а не n, мы получим, что время выполнения равно O (x 2 ), и поэтому алгоритм работает за полиномиальное время.
Точно так же предположим, что вы выполняете поиск в глубину на графе, который занимает время O (m + n), где m - количество ребер в графе, а n - количество узлов. Как это соотносится с количеством введенных битов? Что ж, если мы предположим, что вход задан как список смежности (список всех узлов и ребер), то, как упоминалось ранее, количество входных битов будет x = Ω (m + n). Следовательно, время выполнения будет O (x), поэтому алгоритм работает за полиномиальное время.
Однако все начинает ломаться, когда мы начинаем говорить об алгоритмах, работающих с числами. Давайте рассмотрим проблему проверки того, является ли число простым или нет. Учитывая число n, вы можете проверить, является ли n простым, используя следующий алгоритм:
function isPrime(n):
for i from 2 to n - 1:
if (n mod i) = 0, return false
return true
Так какова временная сложность этого кода? Что ж, этот внутренний цикл выполняется O (n) раз и каждый раз выполняет некоторую работу по вычислению n mod i (как действительно консервативная верхняя граница, это, безусловно, может быть выполнено за время O (n 3 )). Следовательно, этот общий алгоритм работает за время O (n 4 ) и, возможно, намного быстрее.
В 2004 году трое ученых-информатиков опубликовали статью под названием PRIMES is in P, в которой приводится алгоритм с полиномиальным временем для проверки того, является ли число простым. Это считалось знаковым результатом. Так в чем же дело? Разве у нас еще нет для этого полиномиального алгоритма, а именно приведенного выше?
К сожалению, нет. Помните, что формальное определение временной сложности говорит о сложности алгоритма как функции количества битов ввода. Наш алгоритм работает за время O (n 4 ), но как это зависит от количества входных битов? Что ж, запись числа n занимает O (log n) бит. Следовательно, если мы позволим x быть количеством битов, необходимых для записи ввода n, время выполнения этого алгоритма фактически будет O (2 4x ), что не является полиномом от x.
В этом суть различия между полиномиальным и псевдополиномиальным временем. С одной стороны, наш алгоритм O (n 4 ), который выглядит как полином, но с другой стороны, согласно формальному определению полиномиального времени, это не полиномиальное время.
Чтобы понять, почему этот алгоритм не является алгоритмом с полиномиальным временем, подумайте о следующем. Предположим, я хочу, чтобы алгоритм проделал большую работу. Если я напишу такой ввод:
10001010101011
то, скажем T
, для завершения потребуется некоторое время, в худшем случае . Если я теперь добавлю один бит в конец числа, например:
100010101010111
Время выполнения теперь будет (в худшем случае) 2T. Я могу удвоить объем работы, выполняемой алгоритмом, просто добавив еще один бит!
Алгоритм выполняется за псевдополиномиальное время, если время выполнения является некоторым полиномом по числовому значению входных данных , а не по количеству битов, необходимых для его представления. Наш алгоритм первичного тестирования - это алгоритм с псевдополиномиальным временем, поскольку он выполняется за время O (n 4 ), но это не алгоритм с полиномиальным временем, потому что в зависимости от количества битов x, необходимых для записи ввода, время выполнения равно O (2 4 раза ). Причина того, что статья «PRIMES is in P» была настолько важной, заключалась в том, что время ее выполнения было (примерно) O (log 12 n), что как функция количества битов составляет O (x 12 ).
Так почему это важно? Что ж, у нас есть много алгоритмов псевдополиномиального времени для факторизации целых чисел. Однако эти алгоритмы, технически говоря, являются алгоритмами экспоненциального времени. Это очень полезно для криптографии: если вы хотите использовать шифрование RSA, вы должны быть уверены, что мы не можем легко разложить числа на множители. Увеличивая количество бит в числах до огромного значения (скажем, 1024 битов), вы можете сделать количество времени, которое должен занять алгоритм факторинга псевдополиномиального времени, настолько большим, что было бы полностью и совершенно невозможно разложить множители на множители. числа. Если, с другой стороны, мы можем найти алгоритм факторизации за полиномиальное время, это не обязательно так. Добавление большего количества битов может привести к значительному увеличению объема работы, но рост будет только полиномиальным, а не экспоненциальным.
Тем не менее, во многих случаях алгоритмы псевдополиномиального времени вполне подходят, потому что размер чисел не будет слишком большим. Например, подсчетная сортировка имеет время выполнения O (n + U), где U - наибольшее число в массиве. Это псевдополиномиальное время (поскольку числовое значение U требует для записи O (log U) бит, поэтому время выполнения экспоненциально зависит от размера ввода). Если мы искусственно ограничим U, чтобы U не было слишком большим (скажем, если мы позволим U равным 2), то время выполнения будет O (n), что на самом деле является полиномиальным временем. Вот как работает поразрядная сортировка : обрабатывая числа по одному бит за раз, время выполнения каждого раунда составляет O (n), поэтому общее время выполнения составляет O (n log U). Это на самом деле является полиномиальное время, поскольку для записи n чисел для сортировки используется Ω (n) бит, а значение log U прямо пропорционально количеству битов, необходимых для записи максимального значения в массиве.
Надеюсь это поможет!