C++ 关于矢量生长

C++ 关于矢量生长,c++,visual-c++,C++,Visual C++,我读过这本书: C++入门,第三版 斯坦利·B·利普曼、何塞·拉乔伊 到目前为止发现了1个错误。 …在第6.3条中给出的程序中,向量是如何增长的,该程序遗漏了一个“向量的容量完全依赖于实现,没有人知道它是如何增长的。您使用的是“Rogue Wave”实现吗 容量增长的方式取决于实现。您使用的是2^N。是的,每次超过容量时,容量都会翻倍。这取决于实现。向量容量增长的速度取决于实现。实现几乎总是选择指数增长,以满足摊余恒定时间rpush_-back操作的要求。摊销固定时间的含义以及指数增长如何实现这

我读过这本书: C++入门,第三版 斯坦利·B·利普曼、何塞·拉乔伊

到目前为止发现了1个错误。
…在第6.3条中给出的程序中,向量是如何增长的,该程序遗漏了一个“向量的容量完全依赖于实现,没有人知道它是如何增长的。

您使用的是“Rogue Wave”实现吗


容量增长的方式取决于实现。您使用的是2^N。

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

向量容量增长的速度取决于实现。实现几乎总是选择指数增长,以满足摊余恒定时间r
push_-back
操作的要求。摊销固定时间的含义以及指数增长如何实现这一点很有趣

每次向量的容量增加时,都需要复制元素。如果你在向量的生命周期内“摊销”该成本,那么如果你以指数因子增加容量,你最终会得到摊销的固定成本

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

  • 大小:1容量1-未复制任何元素,每个元素的复制成本为0
  • 大小:2容量2-当向量的容量增加到2时,必须复制第一个元素。每个元素的平均副本数为0.5
  • 大小:3容量4-当向量的容量增加到4时,必须复制前两个元素。每个元素的平均副本数为(2+1+0)/3=1
  • 大小:4个容量4-每个元素的平均拷贝数为(2+1+0+0)/4=3/4=0.75
  • 大小:5个容量8-每个元素的平均拷贝数为(3+2+1+1+0)/5=7/5=1.4
  • 大小:8个容量8-每个元素的平均拷贝数为(3+2+1+1+0+0+0+0)/8=7/8=0.875
  • 大小:9个容量16-每个元素的平均拷贝数为(4+3+2+2+1+1+1+0)/9=15/9=1.67
  • 大小16容量16-每个元素的平均拷贝数为15/16=0.938
  • 大小17容量32-每个元素的平均拷贝数为31/17=1.82
正如您所看到的,每次容量跳跃时,拷贝数都会增加上一个数组的大小。但是,由于在容量再次跳跃之前,数组的大小必须增加一倍,因此每个元素的拷贝数始终小于2

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

我怀疑一个实现会选择1.5而不是2,这既节省了一点空间,也因为1.5更接近于。我有一个直觉(目前没有任何硬数据支持),即增长率符合黄金比率(因为它与斐波那契序列的关系)将被证明是现实世界负载在最小化额外空间和时间方面最有效的增长率。

为了能够在
std::vector
末尾提供摊销固定时间插入,实现必须将向量的大小(需要时)增长一个因子
K>1
(*),这样当尝试附加到大小为
N
且已满的向量时,该向量将增长为
K*N

不同的实现使用不同的常数
K
,这提供了不同的好处,尤其是大多数实现使用
K=2
K=1.5
。越高的
K
将使其速度越快,因为它需要的增长越少,但同时会对内存产生更大的影响。例如,在gcc中
K=2
,而在VS(Dinkumware)
K=1.5


(*)如果向量以常量增长,则
推回的复杂性将变为线性而不是摊销常量。例如,如果向量在需要时增长10个元素,则增长的成本(将所有元素复制到新内存地址)将为
O(N/10)
(每10个元素,移动所有内容)或者,
O(N)

只是为了对
vector::push_back
的时间复杂度进行一些数学证明,假设向量的大小是
N
,我们关心的是迄今为止发生的拷贝数,比如说
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>1
增长,否则将无法实现
push_back
摊余固定时间成本。+1用于解释摊余固定时间的含义。需要注意的是,实现确实如此不选择指数增长,这是标准要求的(
push_back
必须以固定时间摊销),他们只选择增长
K
值的系数(标准要求大于1)@你的直觉Wrt黄金比例是正确的。Alexandrescu已经发布了关于COMP.Lang.c++的度量。如果我记得正确的话,它用数据来支持它。尽管它的论点是你有时可以“就位”,因为2的分配器分配给我总是很奇怪。(如果不是错过了第一次真正增长的机会而没有任何空间损失,那么什么是到位增长?@Matthieu M.:“如果不是错过了第一次真正增长的机会而没有任何空间损失,那么到位增长又是什么?“。这是一种折衷。如果您第一次增长,但后来没有使用额外的容量,那么您只是浪费了内存。启用黄金比例或略低于黄金比例的一个优点是丢弃
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
  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)

y = C + 2*C + 3*C + 4*C +  ... (n/C) * C
  = C(1+2+3+...+n/C), say m = n/C
  = C*(m*(m-1)/2)
  = n(m-1)/2

T(n) = y/n = (n(m-1)/2)/n = (m-1)/2 = n/2C - 1/2 = O(n)