std :: vector插入的摊销分析

时间:2011-07-01 16:15:05

标签: c++ algorithm stl stdvector amortized-analysis

我们如何分析std :: vector中后面的插入(push_back)?它的摊销时间是每次插入O(1)。特别是在video in channel9 by Stephan T Lavavejin this ( 17:42 onwards )中,他说为了获得最佳性能,Microsoft实现此方法会将向量的容量提高1.5左右。

这个常数如何确定?

3 个答案:

答案 0 :(得分:16)

假设你的意思是push_back而不是插入,我相信重要的部分是乘以一些常数(而不是每次抓取N个更多的元素),只要你这样做,你就会得到摊销恒定时间。更改因子会改变平均情况和最差情况。

具体地: 如果你的常数因子太大,你将获得良好的平均情况性能,但是最糟糕的情况下表现不佳,特别是当阵列变大时。例如,想象一下,加倍(2x)10000大小的向量只是因为你推送了第10001个元素。编辑:正如迈克尔伯尔间接指出的那样,这里真正的成本可能是你的记忆力比你需要的要大得多。我想补充一点,如果你的因素太大,就会有影响速度的缓存问题。我只想说,如果你的成长比你需要的大得多,就会有实际成本(记忆和计算)。

然而,如果你的常数因素太小,比如说(1.1x)那么你将会有最差的表现,但平均表现不好,因为你将不得不承担重新分配太多次的费用

Also, see Jon Skeet's answer to a similar question previously.(谢谢@Bo Persson)

关于分析的更多信息:假设您有n项目正在推回,并且乘法系数为M。然后,重新分配的数量将大致为Mn)的基准log_M(n)i重新分配的成本与M^iMi次幂成比例。然后,所有回击的总时间将为M^1 + M^2 + ... M^(log_M(n))。推迟次数为n,因此您可以获得此系列(几何系列,并在限制中减少到大约(nM)/(M-1))除以n。这大致是一个常数,M/(M-1)

对于M的大值,你会超出很多并且分配比你经常需要的更多(我在上面提到过)。对于小M(接近1)的值,此常量M/(M-1)变大。这个因素直接影响平均时间。

答案 1 :(得分:7)

你可以做数学试图弄清楚这种事情是如何起作用的。

使用渐近分析的一种流行方法是Bankers方法。你所做的是用额外的费用标记你的所有操作,“保存”它以便以后支付昂贵的操作。


让我们做一些转储假设来简化数学运算:

  • 写入数组费用为1。 (在数组之间插入和移动相同)
  • 分配更大的阵列是免费的。

我们的算法看起来像:

function insert(x){
    if n_elements >= maximum array size:
         move all elements to a new array that
         is K times larger than the current size
    add x to array
    n_elements += 1

显然,当我们必须将元素移动到新数组时,会发生“最坏情况”。让我们尝试通过在插入成本中添加d的常量标记来分摊这一点,每次操作总共(1 + d)

在调整阵列大小之后,我们已经填充了(1 / K)并且没有省钱。 当我们填满阵列时,我们可以确保至少保存d * (1 - 1/K) * N。由于这笔钱必须能够支付所有被移动的N个元素,我们可以找出Kd之间的关系:

d*(1 - 1/K)*N = N
d*(K-1)/K = 1
d = K/(K-1)

一张有用的表:

k    d     1+d(total insertion cost)
1.0  inf   inf
1.1  11.0  12.0
1.5  3.0   4.0
2.0  2.0   3.0
3.0  1.5   2.5
4.0  1.3   2.3
inf  1.0   2.0

因此,你可以得到一个粗略的数学家关于时间/内存权衡如何适用于这个问题的想法。当然有一些注意事项:当元素数量减少时,我没有过多地缩小数组,这只能解决最糟糕的情况,即没有删除任何元素,并且没有考虑分配额外内存的时间成本。 / p>

他们最有可能进行了大量的实验测试,最终弄清楚了这一点,但我写的大部分内容都是无关紧要的。

答案 2 :(得分:1)

嗯,当你熟悉数字系统时,分析非常简单,例如我们常用的十进制数。

为简单起见,假设每次达到当前容量时,都会分配一个新的10倍大缓冲区。

如果原始缓冲区的大小为1,则第一次重新分配将复制1个元素,第二个(现在缓冲区大小为10)复制10个元素,依此类推。因此,通过五次重新分配,您可以执行1 + 10 + 100 + 1000 + 10000 = 11111个元素副本。乘以9,得到99999;现在加1,你有100000 = 10 ^ 5。或者换句话说,向后执行此操作,为支持这5次重新分配而执行的元素副本数量为(10 ^ 5-1)/ 9。

5次重新分配后的缓冲区大小,5次乘以10,为10 ^ 5。这大约是元素复制操作数量的9倍。这意味着复制所花费的时间与生成的缓冲区大小大致呈线性关系。

用2而不是10来得到(2 ^ 5-1)/ 1 = 2 ^ 5-1。

等等其他基础(或增加缓冲区大小的因素)。

干杯&第h