考虑以下循环:
def func1(xs, ys, zs):
for x in xs:
for y in ys:
pass
for z in zs:
pass
运行时间应为size of xs * (size of ys + size of zs)
,可以用O(X) * (O(Y) + O(Z))
的大写符号编写。
def func2(xs, ys, zs):
for y in ys:
for x in xs:
pass
for z in zs:
for x in xs:
pass
此功能的运行时间应为size of ys * size of xs + size of zs * size of xs
,这将在Big-O O(Y) * O(X) + O(Z) * O(X)
中生成。
问题是,这些运行时间分析是否正确?如果是这样,这些功能的运行时间是否相等?因为来自算术x * (y + z) = x * y + x * z
。
ipython %timeit
函数的结果显示我似乎错了。
In [8]: ys1 = range(1, 500)
In [9]: zs1 = range(1, 1000)
In [11]: xs1 = range(1, 1000000)
In [12]: %timeit func1(xs1, ys1, zs1)
1 loop, best of 3: 15.7 s per loop
In [13]: %timeit func2(xs1, ys1, zs1)
1 loop, best of 3: 19.1 s per loop
想了解我的分析有什么问题。感谢。
答案 0 :(得分:2)
您正在测量for循环本身执行的时间,而不是循环中的虚构语句。
当你的循环结构如下:
for x in xs:
for y in ys:
pass
for z in zs:
pass
考虑每个列表迭代的次数:
xs
从外部循环迭代一次ys
在内循环中迭代X
次zs
在内循环中迭代X
次所以总迭代次数为X * (1 + Y + Z)
,扩展为X + XY + XZ
pass
语句执行的次数为X * (Y+Z)
或XY + XZ
,与总迭代次数不同。
当循环的结构改为如下:
for y in ys:
for x in xs:
pass
for z in zs:
for x in xs:
pass
ys
从第一个外循环迭代一次xs
在第一个内循环中迭代Y
次zs
从第二个外循环迭代一次xs
在第二内循环中迭代Z
次意味着迭代总数为Y*(1+X) + Z*(1 + X)
,扩展为XY + XZ + Y + Z
这与第一个等式有很大不同。
但是执行的pass
语句的数量是相同的:XY + XZ
基本上:执行的实际语句的数量是相同的,但是第二个例子的迭代次数(获取列表的下一个元素)通常更大,并且因为pass
花费的时间少于对于循环开销,后者是你实际测量的。
答案 1 :(得分:0)
你想使用O(x * (y + x))
而不是O(X) * (O(Y) + O(Z))
(其中x是X的长度,y是Y的长度等)来描述第一个循环的复杂性(O应该“包裹”整个表达)
为什么?
假设f(x)是返回执行大小为x的任务所需的确切操作数的函数。
根据定义f(x)= O(x)如果存在a,b使得a * O(x)+ b = f(x)对于所有x(注意它也意味着对于所有O(x) ),所有全部b,所有c和所有d,a * O(x) + b
= c * O(x) + d
)。要理解为什么这意味着您需要包装表达式,请考虑以下循环:
for i in range(n):
for j in range(n):
pass # This is assumed to be exactly one action
请注意,对于此循环,f(n)完全等于n^2
假设我们认为此循环的复杂性为O(n) * O(n)
而不是O(n ^ 2)
。然后,O(n)^2
= (a*O(n)+b)^2
= a^2 * O(n) + a * b * O(n) + b ^ 2
。您可以注意到,在扩展中会出现a ^ 2 * O(n)
。它使得不能添加或减去这样的常量,使得该表达式总是等于f(n),因为O(n)显然根据输入大小而改变。因此,这是错误的符号。
O(n ^ 2)虽然恰到好处,但因为a = 1且b = 1,所有n都为a*O(n)+b=f(n)
。
因此,我们需要重写所有循环的复杂性,并牢记这一点。对于第一个循环,正确的复杂度为O(x * (y + z))
= O(x*y + x*z)
。对于第二个循环,正确的复杂度为O(y z + z x)。
所以,两个循环的总体复杂性是相同的。