【问题标题】:Why does adding to the front of the list take quadratic time?为什么添加到列表的前面需要二次时间?
【发布时间】:2015-06-17 08:18:50
【问题描述】:

此代码示例采用 (N^2) 的大 O

results = []

for i in range(1000000)
    result = [f(i)] + results

此代码示例采用 (N) 的大 O

results = []

for i in range(1000000)
    result = results + [f(i)]

为什么这两种算法的Big O有如此明显的区别,唯一的区别是一个加到列表的最前面而另一个加到列表的后面?

这对 Java 也适用吗?

【问题讨论】:

  • "在内部,一个列表表示为一个数组;最大的成本来自于超出当前分配大小的增长(因为所有东西都必须移动),或者来自靠近开头的某处插入或删除(因为之后的所有内容必须移动)。” - wiki.python.org/moin/TimeComplexity#list
  • 这两个都是 O(N^2)。 for i in xrange(1000000): results += [f(i)] 是 O(N)
  • 实际上,当前代码中的 O(N^2) 都不是 O(N^2),因为 result 永远只有一个元素,而 results 始终是空列表。这两个语句都没有重新分配 results 变量。
  • result 和 results 之间的区别是错字吗?
  • dupe?

标签: python list


【解决方案1】:

因为列表针对追加而非前置进行了优化:如果您进行前置,则需要重新创建整个列表。

如果您想要一个可以在两端附加效率相同的数据结构,请使用collections.deque。

【讨论】:

  • 列表没有重新创建,所有元素只是在内存中向上移动以在开始时腾出空间,但它仍然是二次的。
  • 这取决于,只有在数组末尾确实有空间时才能移动元素。
  • 我喜欢这个答案,并且应该推断这专门适用于 python,但可能会导致用户相信这适用于所有实现中的所有列表,这是不正确的。你能注意到这是特定于 python 实现的吗?
【解决方案2】:

之所以会出现这种情况,是因为 CPython 是用 C 语言实现的,并且使用 C 数组作为列表的后备存储,在将元素添加到列表前面之前,您必须重新分配和移动元素。

回答您问题的后半部分,“Java 是否也如此?”答案是肯定的,Java 中的原始数组和 Java 中的 ArrayList 都不允许在不重新分配和移动元素的情况下添加到列表中。

另外,您的代码存在缺陷,因为您分配给result 而不是results,因此您实际上并没有导致问题中所述的二次性能。演示此行为的更好方法如下:

results = []
for i in range(1000000):
    results.insert(0, f(i))

以及附加版本:

results = []
for i in range(1000000):
    results.append(f(i))

或者使用timeit 模块:

$ python -m timeit 'results = []' 'for i in range(10000): results.insert(0, i)'
10 loops, best of 3: 50.9 msec per loop
$ python -m timeit 'results = []' 'for i in range(10000): results.append(i)'
1000 loops, best of 3: 794 usec per loop

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-30
    • 1970-01-01
    • 2012-02-23
    • 2021-04-30
    • 1970-01-01
    相关资源
    最近更新 更多