关于向量增长

时间:2011-03-08 12:06:27

标签: c++ visual-c++

一直在阅读这本书: C ++ Primer,第三版 作者:Stanley B. Lippman,JoséeLajoie

发现1个错误,直到现在。 ......在第6.3条规定的程序中,该程序如何生成,本程序错过了“<”在couts !! 给出的计划是:

#include <vector>
#include <iostream>

int main(){
vector< int > ivec;
cout < "ivec: size: " < ivec.size()
< " capacity: "  < ivec.capacity() < endl;

for ( int ix = 0; ix < 24; ++ix ) {
ivec.push_back( ix );
cout < "ivec: size: " < ivec.size()
< " capacity: "  < ivec.capacity() < endl;
}    
}

现在我纠正了这个问题。在该文章的后面,该书说明如下: “在Rogue Wave实现下,ivec定义后的大小和容量均为0.但是,插入第一个元素时,ivec的容量为256,其大小为1。”

但是,在纠正和运行代码时,我得到以下输出:


ivec: size: 0 capacity: 0
ivec[0]=0 ivec: size: 1 capacity: 1
ivec[1]=1 ivec: size: 2 capacity: 2
ivec[2]=2 ivec: size: 3 capacity: 4
ivec[3]=3 ivec: size: 4 capacity: 4
ivec[4]=4 ivec: size: 5 capacity: 8
ivec[5]=5 ivec: size: 6 capacity: 8
ivec[6]=6 ivec: size: 7 capacity: 8
ivec[7]=7 ivec: size: 8 capacity: 8
ivec[8]=8 ivec: size: 9 capacity: 16
ivec[9]=9 ivec: size: 10 capacity: 16
ivec[10]=10 ivec: size: 11 capacity: 16
ivec[11]=11 ivec: size: 12 capacity: 16
ivec[12]=12 ivec: size: 13 capacity: 16
ivec[13]=13 ivec: size: 14 capacity: 16
ivec[14]=14 ivec: size: 15 capacity: 16
ivec[15]=15 ivec: size: 16 capacity: 16
ivec[16]=16 ivec: size: 17 capacity: 32
ivec[17]=17 ivec: size: 18 capacity: 32
ivec[18]=18 ivec: size: 19 capacity: 32
ivec[19]=19 ivec: size: 20 capacity: 32
ivec[20]=20 ivec: size: 21 capacity: 32
ivec[21]=21 ivec: size: 22 capacity: 32
ivec[22]=22 ivec: size: 23 capacity: 32
ivec[23]=23 ivec: size: 24 capacity: 32

因此,初始容量随着公式2而增加.N不是它?其中 N 是初始容量。 请解释一下。

6 个答案:

答案 0 :(得分:62)

向量容量增长的速率取决于实现。实现几乎总是选择指数增长,以满足push_back操作的摊销的常量时间要求。什么摊销的常数时间意味着指数增长如何实现这一目标很有意思。

每次向量的容量增长时,都需要复制元素。如果你在向量的整个生命周期内“摊销”这个成本,事实证明,如果你以指数因子增加容量,你最终会得到一个摊销的固定成本。

这可能看起来有点奇怪,所以让我向你解释一下这是如何工作的......

  • size:1 capacity 1 - 未复制任何元素,每个元素的成本为0。
  • size:2 capacity 2 - 当向量的容量增加到2时,必须复制第一个元素。每个元素的平均拷贝数为0.5
  • size:3 capacity 4 - 当向量的容量增加到4时,必须复制前两个元素。每个元素的平均副本是(2 + 1 + 0)/ 3 = 1.
  • size:4 capacity 4 - 每个元素的平均拷贝数为(2 + 1 + 0 + 0)/ 4 = 3/4 = 0.75。
  • size:5 capacity 8 - 每个元素的平均拷贝数为(3 + 2 + 1 + 1 + 0)/ 5 = 7/5 = 1.4
  • ...
  • size:8 capacity 8 - 每个元素的平均拷贝数为(3 + 2 + 1 + 1 + 0 + 0 + 0 + 0)/ 8 = 7/8 = 0.875
  • size:9 capacity 16 - 每个元素的平均拷贝数为(4 + 3 + 2 + 2 + 1 + 1 + 1 + 1 + 0)/ 9 = 15/9 = 1.67
  • ...
  • size 16 capacity 16 - 每个元素的平均拷贝数为15/16 = 0.938
  • size 17 capacity 32 - 每个元素的平均拷贝数是31/17 = 1.82

正如您所看到的,每次容量跳跃时,副本数量都会增加前一个数组的大小。但是因为在容量再次跳转之前阵列必须加倍,所以每个元素的副本数总是小于2。

如果你将容量增加了1.5 * N而不是2 * N,那么你最终会得到一个非常相似的效果,除了每个元素的副本上限会更高(我认为它会是3)。

我怀疑实现会选择1.5以上两者来节省一点空间,但也因为1.5更接近golden ratio。我有一种直觉(目前没有任何硬数据支持),与黄金比率一致的增长率(因为它与斐波纳契序列的关系)将被证明是实际负载的最有效增长率在最小化使用的额外空间和时间方面。

答案 1 :(得分:11)

为了能够在std::vector的末尾提供分摊的常量时间插入,实现必须增加向量的大小(在需要时){{1} }(*),这样当试图追加大小为K>1的向量时,向量增长为N

不同的实现使用不同的常量K*N来提供不同的好处,特别是大多数实现都适用于KK = 2。较高的K = 1.5会使其更快,因为它需要较少的增长,但它会同时产生更大的内存影响。例如,在gcc K中,而在VS(Dinkumware)K = 2中。

(*)如果向量增长的数量恒定,则K = 1.5的复杂度将变为线性而非摊销常数。例如,如果向量在需要时增长了10个元素,则增长的成本(所有元素到新内存地址的副本)将是push_back(每10个元素,移动所有元素)或O( N / 10 )

答案 2 :(得分:1)

vector的容量完全取决于实现,没有人能说出它的增长情况。

答案 3 :(得分:1)

您使用的是“Rogue Wave”实施吗?

容量如何增长取决于实施。你的使用2 ^ N。

答案 4 :(得分:1)

只需在vector::push_back上添加一些关于时间复杂度的数学证明,例如向量的大小为n,我们在这里关心的是到目前为止发生的拷贝数,例如{{1 }},请注意,每次增加向量时都会发生复制。

增长系数 y

K

y = K^1 + K^2 + K^3 ... K^log(K, n) K*y = + K^2 + K^3 ... K^log(K, n) + K*K^log(K, n) K*y-y = K*K^log(K, n) - K y = K(n-1)/(K-1) = (K/(K-1))(n-1) T(n) = y/n = (K/(K-1)) * (n-1)/n < K/(K-1) = O(1) 是一个常数,请参见最常见的情况:

  • K = 2,T(n)= 2 /(2-1)= 2
  • K = 1.5,T(n)= 1.5 /(1.5-1)= 3

实际上,在不同的实现中有一个选择K为1.5或2的原因,请参见此graph:当K/(K-1)接近2时,T(n)达到最小值,没有使用更大的K会带来很多好处,但会分配更多的内存

以恒定的数量增长 K

C

我们可以看到它是衬纸

答案 5 :(得分:0)

是的,每次超过时容量都会翻倍。这取决于实现。