"因此,插入N个元素需要总计O(N)。每次插入平均为O(1),尽管某些插入在最坏的情况下需要O(N)时间。"这句话可以在Cracking the Coding Interview中找到。我有点理解这句话,尽管有一点关于它的事情让我感到厌烦。在好日子,摊销的插入是O(1)。这只是意味着当可调整大小的数组不需要调整大小时,插入一些东西就是O(1)。这很清楚。但是,在糟糕的一天,当我们用完空间时,我们需要O(N)来插入额外的元素。但是,我不同意上面的陈述,当它说某些插入在最坏的情况下采取O(N)。不应该说,在最坏的情况下,一次插入需要O(N)。
为了说明这一点,以下是我所说的一个例子。
假设我们有一个可调整大小的数组,但我们的数组大小为4.我们现在说插入5个元素,我们将有O(1),O(1),O(1),O( 1),但是一旦我们到达最后一个元素,我们将不得不将所有这些人复制到一个新的数组中,这个过程会给我们一个O(N)的成本。
有人可以帮我澄清一下。我不明白为什么当我们在旧阵列中运行空间时,只需要将所有元素复制到新数组中时,本书会说某些情况需要O(N)。
答案 0 :(得分:1)
我认为你最好像这样理解上述陈述。
起初。数组大小只是1.并插入一个元素。现在数组已经满了!你必须把它的大小调整为前一次的2倍。
接下来,数组大小为2.让此过程进行。您可以很容易地注意到,必须调整数组大小的时刻是1,2,4,8,16,32,...,2 ^ r。
我会给你问题。
第一个答案是楼层(lgN)次。我想你可以轻松搞清楚。如果你找到第一个答案,计算这N个步骤的总成本是第二个答案很容易。(我不知道如何表达数学符号:<)
1 + 2 + 4 + 8 + 16 + ... + 2 ^(floor(lgN))= 2 ^(floor(lgN)+1) - 1 =>为O(n)
要获得每个步骤的平均成本,请将总成本除以N => O(1)
我认为参考文献提到的最坏情况是需要调整数组的大小。这种重新调整的成本与数组中元素的数量成比例,O(N)
答案 1 :(得分:1)
以可调整大小的数组内N次插入的代价为例(我将在此处使用代字号):
这只是在数组中插入新元素的成本乘以插入新元素(即N :)的次数乘以
想象一下,您有一个64个单元的阵列。然后,这意味着该数组的大小已调整为:
已将64个单元阵列的大小调整了6倍,即,调整大小发生了log2(64)次。 通常,现在我们知道对于N插入,我们将执行log2(N)调整大小操作。
但是,每次调整大小后我们要做什么?我们将在新的调整大小的数组中复制数组中已经存在的元素:在调整大小“ i”时,我们将复制多少个元素? 2 ^ i。对于前面的示例:
所以:
答案 2 :(得分:0)
std::vector
会将所有元素保持在内存中以便快速迭代 - 这是它的事情,它的作用是什么,这就是为什么每个人都喜欢{{1} 1}}。通常它会保留比其中包含的元素数量所需的空间更多的空间,或者内存恰好是免费的,所以当你在std::vector
的末尾添加一个新元素时,它会很快让vector
将你的新元素推到那里。
然而,当vector
没有空间展开时,它不能将现有元素留在原处并在其他地方开始新列表 - 所有元素必须紧挨着彼此在记忆中!所以它必须找到一大块内存,对于所有元素和新元素都足够大,然后将所有现有元素复制到那里,然后将新元素添加到最后。
如果添加1个元素需要1个单位时间,从广义上讲,N个元素需要N个单位。如果添加新元素,那就是一个操作。如果添加新元素,则需要重新定位1024个现有元素,即1025次操作。因此,重新分配所需的时间与向量的大小成正比,因此vector
。
答案 3 :(得分:0)
让我们将所有插入分成“重”插入,这些插入需要时间与元素数量和“光”插入成比例,这些插入只需要花费一定的时间来完成。然后,如果你从一个空列表开始并继续附加一个附加,你将会有大部分轻量插入,但是你时不时会有大量的插入。
让我们说,为简单起见,每次空间用完时你都要加倍数组的大小,然后用大小为4的数组开始。然后第一个调整大小将需要移动四个元素,第二个将移动八,然后十六,然后是三十二,然后六十四,然后是128,然后是256,等等。
请注意,它不仅仅是一个需要花费很长时间的单个追加 - 粗略地说,如果你有n次插入,那么它们的大致log n将很重(它们之间的时间不断增长)而另一个大概是n - 他们的日志会很轻。