什么是伪多项式时间?它与多项式时间有何不同?


Answers:


253

要了解多项式时间和伪多项式时间之间的差异,我们需要从形式上定义“多项式时间”的含义开始。

多项式时间的直觉是“ 某个k的时间O(n k)”。例如,选择排序在时间O(n 2)上运行,这是多项式时间,而蛮力求解TSP则需要时间O(n·n!),这不是多项式时间。

这些运行时都引用一些跟踪输入大小的变量n。例如,在选择排序中,n表示数组中元素的数量,而在TSP中,n表示图中节点的数量。为了标准化“ n”在这里的实际含义,时间复杂度的正式定义定义了问题的“大小”,如下所示:

问题输入的大小是写出该输入所需的位数。

例如,如果排序算法的输入是32位整数的数组,则输入的大小将为32n,其中n是数组中的条目数。在具有n个节点和m个边的图中,输入可能被指定为所有节点的列表,然后是所有边的列表,这将需要Ω(n + m)位。

给定此定义,多项式时间的形式定义如下:

如果某个常数k的运行时间为O(x 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 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 4x)。“ PRIMES 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的值与写出数组中最大值所需的位数成正比。

希望这可以帮助!



4
为什么将isPrime复杂度估计为O(n ^ 4)而不是简单地估计为O(n)?我不明白 除非的复杂度n mod i为O(n ^ 3).... 否则肯定不是。
2013年

4
@没人通常,我们认为将两个数字相乘的成本为O(1),但是当您处理任意大的数字时,乘数的成本随数字本身的大小而增加。保守起见,我声称您可以将小于n的数字计算为O(n ^ 3),这虽然有点过高,但还算不错。
templatetypedef

1
@AndrewFlemming这取决于数字在内存中的表示方式。我以为我们使用的是标准的二进制表示形式,这里我们需要log_2个n位来表示数字。没错,更改基础表示形式会根据输入大小来更改运行时。
templatetypedef

1
选择O(n ^ 3)n mod i过于保守。的时序mod是in中位数的函数n,而不是其n本身的位数,因此应为O((log n)^ 3)。
dasblinkenlight

2

伪多项式时间复杂度是指输入值/幅度为多项式,但输入大小为指数。

所谓大小,是指写入输入所需的位数。

从背包的伪代码中,我们可以发现时间复杂度为O(nW)。

// Input:
// Values (stored in array v) 
// Weights (stored in array w)
// Number of distinct items (n) //
Knapsack capacity (W) 
for w from 0 to W 
    do   m[0, w] := 0 
end for  
for i from 1 to n do  
        for j from 0 to W do
               if j >= w[i] then 
                      m[i, j] := max(m[i-1, j], m[i-1, j-w[i]] + v[i]) 
              else 
                      m[i, j] := m[i-1, j]
              end if
       end for 
end for

在这里,W不是输入长度的多项式,这就是使其成为伪多项式的原因。

令s为表示W所需的位数

i.e. size of input= s =log(W) (log= log base 2)
-> 2^(s)=2^(log(W))
-> 2^(s)=W  (because  2^(log(x)) = x)

现在,running time of knapsack= O(nW)= O(n * 2 ^ s),这不是多项式。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.