为啥要双倍堆叠容量而不是仅仅增加固定数量?
Posted
技术标签:
【中文标题】为啥要双倍堆叠容量而不是仅仅增加固定数量?【英文标题】:Why double stack capacity instead of just increasing it by fixed amount?为什么要双倍堆叠容量而不是仅仅增加固定数量? 【发布时间】:2012-05-12 06:03:03 【问题描述】:我正在使用堆栈的数组实现,如果堆栈已满而不是抛出错误,我将数组大小加倍,复制元素,更改堆栈引用并将新元素添加到堆栈中。 (我正在按照一本书自学这些东西)。
我不完全明白的是为什么要加倍,为什么不固定增加,为什么不增加3倍。
我认为它与时间复杂度或其他什么有关?
非常感谢您的解释!
【问题讨论】:
问题标题没有意义。您可能需要改写它。 仅供参考 2 是 Java 中堆栈的标准实现所使用的因素。 【参考方案1】:加倍刚刚成为诸如数组列表(“动态”大小的数组,实际上只是在后台执行您正在执行的操作)和由数组支持的最动态大小的数据类型之类的通用实现的标准。如果您知道您的场景并且有时间和意志力来编写自定义堆栈/数组列表实现,您当然可以编写一个更优化的解决方案。
如果您在软件中知道在构建初始数组后会极少添加项目,您可以将其初始化为特定大小,然后仅将其增加为保留内存所添加的大小。
另一方面,如果您知道列表会非常频繁地扩展,您可能会选择在空间不足时将列表大小增加 3 倍或更多。
对于作为公共库一部分的通用实现,您的实现细节和要求是未知的,因此加倍只是一个快乐的媒介。
【讨论】:
【参考方案2】:理论上,您确实会达到不同的时间复杂度。如果你增加一个常数大小,你将重新分配的数量(因此 O(n) 个副本)除以一个常数,但你仍然会得到 O(n) 的附加时间复杂度。如果将它们加倍,则追加的时间复杂度会更高(平均化 O(1) IIRC),并且由于最多消耗两倍于所需的内存,因此空间复杂度仍然相同。
在实践中,它不那么严重,但仍然可行。副本很昂贵,而一些内存通常不会受到伤害。这是一个折衷方案,但您必须非常低的内存才能选择另一种策略。通常,您事先不知道(或由于 API 限制而无法让堆栈知道)您实际需要多少空间。例如,如果你从一个元素开始构建一个 1024 元素堆栈,你会从 1024/K 减少到(我可能会偏离一个)10 次重新分配——假设 K=3,这大约是 34 倍多次重新分配,只是为了节省一点内存。
对于任何其他因素也是如此。 2 很好,因为你永远不会得到非整数大小,而且它仍然很小,将浪费的空间限制在 50%。其他因素可能更好地服务于特定用例,但通常投资回报率太小,无法证明重新实现和优化某些库中已有的内容是合理的。
【讨论】:
【参考方案3】:固定数量的问题在于选择固定数量 - 如果您(例如)选择 100 个项目作为您的固定数量,那么如果您的堆栈当前大小约为 100 个项目,这是有道理的。但是,如果您的堆栈大小已经有 10,000 个项目,则可能会增长到 11,000 个项目。您不想进行 10 次重新分配/移动以将堆栈大小增加 10%。
至于 2x 与 3x,这是相当随意的 - 选择 3x 没有错;哪个“更好”取决于您的确切用例以及您如何定义“更好”。
【讨论】:
【参考方案4】:按 2 倍缩放很容易,并且将确保平均复制项目不超过两次 [扩展将第一次复制一半的项目,第二次复制四分之一,第三次复制八分之一,等等]如果事情反而增长了一个固定的数量,那么当例如第20次展开,第10次复制一半的item。
增长超过 2 倍将增加平均“永久”空闲空间;以较小的因子增长将增加分配和放弃的存储量。根据永久和废弃分配的相对感知“成本”,最佳增长因子可能更大或更小,但接近最佳的增长因子通常不会比最佳增长因子表现得差太多时间>。无论最佳增长因子是多少,2 倍的增长因子都足以产生不错的性能。
【讨论】:
以上是关于为啥要双倍堆叠容量而不是仅仅增加固定数量?的主要内容,如果未能解决你的问题,请参考以下文章