为什么经典的向量实现(Java人使用的ArrayList)在每次扩展时将其内部数组大小加倍,而不是三倍或四倍?
为什么经典的向量实现(Java人使用的ArrayList)在每次扩展时将其内部数组大小加倍,而不是三倍或四倍?
计算向量插入的平均时间时,需要考虑非扩展插入和扩展插入。
将插入n项的总操作数称为ototal,平均值为oaverage。
如果插入n项,并根据需要增长A倍,则有 ototal = n + ΣAi [ 0 < i < 1 + lnAn ]个操作。在最坏情况下,你使用了分配存储的1/A。
直观地说,A=2表示最坏情况下有ototal=2n,因此oaverage是O(1),并且最坏情况下使用了50%的已分配存储。
对于更大的A,你拥有更低的ototal,但浪费的存储空间更多。
对于较小的A,ototal更大,但不会浪费太多存储空间。只要它几何增长,仍然是O(1)的摊销插入时间,但常数会变高。
对于增长因子为1.25(红色)、1.5(青色)、2(黑色)、3(蓝色)和4(绿色)的情况,这些图表显示了在插入400,000项时左侧的点和平均大小效率(大小/分配空间比率;数字越大表示更好),右侧的时间效率(插入操作/总操作比率;数字越大表示更好)。所有增长因子在重新调整大小之前都达到了100%的空间效率;对于A=2的情况,时间效率在25%至50%之间,空间效率约为50%,这对于大多数情况而言是不错的:
对于像Java这样的运行时环境,数组是零填充的,因此分配操作的数量与数组大小成正比。考虑到这一点可以减少时间效率估计之间的差异:
任何倍数都是一种妥协。如果太大,会浪费太多内存。如果太小,会浪费很多时间进行重新分配和复制。我猜翻倍是因为它有效且非常容易实现。我还看到一种类似于STL的专有库使用1.5作为相同的乘数 - 我猜开发人员认为翻倍会浪费太多内存。
个人认为这是任意选择。我们可以使用自然对数e作为底数,而不是2的底数(不是加倍,而是将大小乘以(1+e)。)
如果您要向向量添加大量变量,则具有高基数将是有利的(以减少您将要复制的数量)。另一方面,如果您只需要存储平均数很少的成员,则较低的基数就可以了,并减少开销,从而加快速度。
二进制是一种折中方案。
在大 O 性能方面,将数量翻倍、三倍或四倍没有性能上的区别。然而,在绝对值方面,翻倍通常在正常情况下更节省空间。