为什么python的list.append()方法的时间复杂度为O(1)?

时间:2015-10-09 18:24:38

标签: python python-2.7 time-complexity amortized-analysis

正如TimeComplexity的文档中所见,Python的list类型实现的是使用数组。

因此,如果正在使用数组并且我们做了一些追加,最终您将不得不重新分配空间并将所有信息复制到新空间。
毕竟,怎么可能是O(1)最坏的情况呢?

4 个答案:

答案 0 :(得分:23)

它是摊销O(1),而不是O(1)。

假设列表保留大小为8个元素,当空间用完时它的大小加倍。你想推50个元素。

前8个元素按O(1)。 第九个触发重新分配和8个副本,然后是O(1)推送。 O(1)中的下一个7推。 第17个触发重新分配和16个副本,然后是O(1)推送。 接下来的15次推进O(1)。 第三十三个触发重新分配和32个副本,然后是O(1)推送。 接下来的17次推进O(1)。

因此所有推动都具有O(1)复杂度,我们在O(1)处有56个副本,在O(n)处有3个重新分配,n = 8,16和32.注意这是几何系列和渐近等于O(n),其中n =列表的最终大小。这意味着将n个对象推送到列表上的整个操作是O(n)。如果我们分摊每个元素,那就是O(n)/ n = O(1)。

答案 1 :(得分:19)

如果您查看链接的文档中的脚注,您可以看到它们包含一个警告:

  

这些操作依赖于" Amortized" " Amortized Worst的一部分   情况下&#34 ;.个人行为可能需要很长时间,具体取决于   容器的历史。

使用amortized analysis,即使我们偶尔执行昂贵的操作,我们也可以获得平均值的下限。将它们视为序列时的操作成本,而不是单独的。

因此,任何单独的操作都可能非常昂贵--O(n)或O(n ^ 2)或更大的东西 - 但由于我们知道这些操作很少,我们保证一系列O(n)操作可以在O(n)时间完成。

答案 2 :(得分:2)

追加到列表的时间成本

Python的设计人员使用了一个非常聪明的想法,以确保不会偶然发生追加到列表的最坏情况运行时间。我们刚刚看到,当Python附加到现有列表并且空间不足时,它必须请求一大块内存,它将移动列表。

假设列表长度为n。但是,不是为size n + 1列表请求足够的空间,Python安排为新列表增长留出空间,并为size 2n列表请求空间。以这种方式进行全面定位可能看起来好像浪费了大量空间 - Python分配的列表实际需要的内存量高达twice。但优点是,如果您反复将项目附加到列表中,则不经常移动列表。

答案 3 :(得分:1)

这很容易。

我们可以通过累加将n个元素追加到arraylist并将其除以n的总时间来计算。

首先,我们需要重定位log(n)次,每次重定位都要加倍2。因此我们有一个比例序列,其比率为2,长度为log(n)。

一个比例系列之和是a(1-r ^ n)/(1-r) 因此,重新安置的总时间为(1-n)/(1-2)= n 时间复杂度为n / n = 1。