对于列表对象,
temp = temp + []
创建一个新列表,并在结果列表的大小上花费线性时间(它线性地缩放)。重要的是,它重新创建了整个新列表。如果在循环中完成,例如
x = []
for i in range(N):
x = x + [i]
整个算法是二次时间,O(N^2)
另一方面,temp += [] 在原地工作。它不会创建新列表。它是 O(K),其中 K 是右侧列表的大小,即添加的元素数。这是因为 python 列表对象被实现为数组列表,overallocate 所以你不必在每次列表大小增加时重新分配。简单地说,这意味着将一个项目附加到列表的末尾是摊销常数时间。重要的是,这使得:
x = []
for i in range(N):
x += [i]
线性时间,即 O(N)。
要凭经验查看此行为,您可以使用以下脚本:
import pandas as pd
import matplotlib.pyplot as plt
import time
def concatenate(N):
result = []
for i in range(N):
result = result + [i]
def inplace(N):
result = []
for i in range(N):
result += [i]
def time_func(N, f):
start = time.perf_counter()
f(N)
stop = time.perf_counter()
return stop - start
NS = range(0, 100_001, 10_000)
inplc = [time_func(n, inplace) for n in NS]
concat = [time_func(n, concatenate) for n in NS]
df = pd.DataFrame({"in-place":inplc, "concat": concat}, index=NS)
df.plot()
plt.savefig('in-place-vs-new-list-loop.png')
注意,在N == 100_000,串联版本需要 10 多秒,而就地扩展版本需要 0.01 秒......所以它慢了几个数量级,并且差异将继续显着增长(即二次方)随着 N 的增加而增加。
为了理解这种行为,这里是时间复杂度的非正式处理:
对于 concat,在每次迭代中,x = x + [i] 需要 i 的工作量,其中 i 是 结果数组的长度。所以整个循环将是0 + 1 + 2 + 3 + ... + N。现在,使用 handy formula for the Nth partial sum of this well-known series 循环将需要 N*(N+1)/2 总工作。
N*(N + 1) / 2 == N^2/2 + N/2 这就是 O(N^2)
另一方面,就地扩展版本,在每次迭代中,
temp += [i]
只需要 1 (恒定) 的工作量。所以对于整个循环,它只是
1 + 1 + ... + 1(N次)
所以N总工作量,所以是O(N)