用于实现堆栈的链接列表与动态数组

时间:2011-09-13 23:39:27

标签: performance data-structures stack linked-list dynamic-arrays

我开始在学校最后一年开始之前检查数据结构和算法,以确保我掌握一切。一个评论问题是“使用链表或动态数组实现堆栈并解释您为何做出最佳选择”。

对我来说,使用带有尾指针的列表来实现堆栈似乎更直观,因为它可能需要经常调整大小。对于大量数据来说,列表是更好的选择,因为动态数组重新调整大小是一项昂贵的操作。此外,使用列表,您不需要分配比实际需要更多的空间,因此空间效率更高。

但是,动态数组肯定允许更快地添加数据(除非需要调整大小)。但是,我不确定使用数组是否整体更快,或者只是不需要调整大小。

本书的解决方案说“对于存储非常大的对象,列表是更好的实现”但我不明白为什么。

哪种方式最好?应该使用哪些因素来确定哪种实施方式“最佳”?此外,我的逻辑是什么?

5 个答案:

答案 0 :(得分:30)

这里涉及许多权衡,我认为这个问题没有“正确”的答案。

如果使用带有尾指针的链表实现堆栈,那么推送,弹出或查看的最坏情况运行时是O(1)。但是,每个元素都会有一些额外的开销(即指针),这意味着结构总是有O(n)开销。此外,根据内存分配器的速度,为堆栈分配新节点的成本可能会很明显。此外,如果您不断地从堆栈中弹出所有元素,则可能会从较差的位置获得性能损失,因为无法保证链接列表单元格将连续存储在内存中。

如果使用动态数组实现堆栈,则推送或弹出的分摊运行时为O(1),最差情况下的查看成本为O(1)。这意味着如果您关心堆栈中任何单个操作的成本,这可能不是最好的方法。也就是说,分配很少,因此添加或删除n个元素的总成本可能比基于链表的方法中的相应成本更快。此外,此方法的内存开销通常优于链表的内存开销。如果动态数组只存储指向元素的指针,那么最坏情况下的内存开销会在填充一半元素时发生,在这种情况下会有n个额外的指针(与使用链接时的情况相同)在动态数组已满的最佳情况下,没有空单元格,额外开销为O(1)。另一方面,如果动态数组直接包含元素,那么在最坏的情况下,内存开销会更糟。最后,因为元素是连续存储的,所以如果你想连续地从堆栈中推送或弹出元素,那么就有更好的局部性,因为所有元素在内存中都是紧挨着的。

简而言之:

  • 链接列表方法在每次操作时都有最坏情况的O(1)保证;动态数组已经摊销了O(1)保证。
  • 链表的位置不如动态数组的位置好。
  • 动态数组的总开销可能小于链表的总开销,假设两个存储指针都指向它们的元素。
  • 如果元素直接存储,动态数组的总开销可能会大于链表的开销。

这两种结构都不比其他结构明显“好”。这真的取决于你的用例。找出哪个更快的最佳方法是将两者都计时,看看哪个表现更好。

希望这有帮助!

答案 1 :(得分:1)

好吧,对于小对象与大对象的问题,如果你的堆栈上有小对象,请考虑为链表使用多少额外空间。然后考虑如果你的堆栈上有一堆大型对象,你需要多少额外的空间。

接下来,考虑相同的问题,但是使用基于动态数组的实现。

答案 2 :(得分:1)

重要的是在运行任务的过程中调用malloc()的次数。可能需要数百到数千条指令才能获得一块内存。 (free()或GC中的时间应该与之成正比。)另外,要保持透视感。这可能是总时间的99%,或者仅为1%,具体取决于其他情况。

答案 3 :(得分:1)

我想你自己回答了这个问题。对于具有大量项目的堆栈,当简单地将额外项目添加到堆栈顶部时,动态阵列将具有过多的开销成本(复制开销)。使用列表,它是一个简单的指针切换。

答案 4 :(得分:1)

如果您很好地设计实现,调整动态数组的大小并不是一项昂贵的任务。

例如,要增长数组,如果已满,请创建一个大小为两倍的新数组,并复制项目。

对于添加N个项目,最终会产生约3N的摊销成本。