我们如何分析std :: vector中后面的插入(push_back)?它的摊销时间是每次插入O(1)。特别是在video in channel9 by Stephan T Lavavej和in this ( 17:42 onwards )中,他说为了获得最佳性能,Microsoft实现此方法会将向量的容量提高1.5左右。
这个常数如何确定?
答案 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
。然后,重新分配的数量将大致为M
(n
)的基准log_M(n)
。 i
重新分配的成本与M^i
(M
到i
次幂成比例。然后,所有回击的总时间将为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个元素,我们可以找出K
和d
之间的关系:
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