是否可以在一个阵列中以O(1)推送/弹出时间实现三个堆栈?


9

使用一个固定大小的数组可以有效地实现两个堆栈:堆栈#1从左端开始向右增长,而堆栈#2从右端开始向左增长。三个堆栈是否可能相同?

更具体地说,在以下条件下是否可以实现三个堆栈:

  1. 您有一个固定大小的数组,可以容纳N个对象。
  2. 只要这三个堆栈大小之和小于N,push()就不会失败。
  3. push()和pop()操作都应花费O(1)时间。
  4. 除了数组之外,您只能使用O(1)额外的空间。

这里是指那些解决方案的例子并不满足这些要求:

  • 将数组拆分为3个固定部分,并将每个部分用于堆栈(违反2)。
  • 与上述类似,但堆栈之间有可移动的边界(违反3)。
  • 基于简单链表的实现(违反4)。

即使它们不能完全满足所有条件(1)-(4),我也会接受非平凡的算法或不可能证明,例如,推/弹出需要O(1)摊销时间的算法,或者额外的内存小于O(N),例如O(log N)。或者说不可能的证据表明,例如,每个推送/弹出操作访问少于5个数组元素是不可能的。


1
我不知道您是否认为这违反了要求4,但是如果N个对象数组中的每个“对象”都可以包含一个附加字段(例如整数索引),那么您可以在数组内部实现“链接列表” 。您可以使用3个外部变量保存3个堆栈中每个堆栈的顶部索引,每个“对象”可以指向其堆栈中的上一个元素。
阿维塔尔(Avi Tal)

“对象”是指push()接受并pop()返回的事物。从堆栈实现的角度来看,它们只是不透明的数据块(例如,一个对象可以是32位整数)。堆栈实现不应以任何方式修改这些对象。
user1020406

1
考虑您首先执行推操作序列,然后仅执行弹出操作。关于此版本的问题是否已知?N
Dmitri Urbanowicz

将的额外空间,你满意吗?O(N)
Dmitri Urbanowicz '19年

回复:“先推后推N次”版本:我不知道,但是即使将其识别为一个有趣的子问题也是有用的,因为即使不清楚O(1)解决方案是否可行。有关上限,请参见@Alexei的答案及其注释线程。至于解决方案,是的,我接受。我是刚开始在stackexchange上发布问题的新手,所以我不确定如何处理随着时间的推移可以提供越来越好的解决方案的情况。我见过的一种方法是等待一天左右才能接受答案,以防出现更好的情况,所以我会这样做。O(N)
user1020406

Answers:


6

弗雷德曼(Fredman)和戈德史密斯(Goldsmith)在《三叠》(算法杂志,1994)中指出 Θ(nε)浪费的空间是可以实现的。对于大小至少为16 quat十亿亿兆字节的数组,这也是最低要求。我描述了一个简单的算法浪费Θ(n) 我StackOverflow中的空格回答了这个问题。正如@ dmitri-urbanowicz在评论中提到的,这基本上只是将数组视为n 大小块 n,其中每个块仅用于一个堆栈,并且包含指向该堆栈中下一个块的单个指针。


0

令N为基础数组的长度。我可以将堆栈想象成大块的链表,因此块的总数不超过O(log2(N))。将第三个堆栈放在前两个之间,索引为N / 2。因此,我们有3个占用区和2个空闲区。当堆栈不能接受下一个元素时,这意味着将耗尽一个空闲区域。如果另一个也被耗尽,那么整个内存就被耗尽。否则,存在另一个不超过N / 2的空闲区域。继续将溢出的堆栈放入该空闲区域。因此整个配置类似于堆栈的初始布局。由于现在的可用内存不超过初始内存的一半,因此此类链接操作的数量不超过log2(N)。每个链接操作都需要固定数量的内存,以保存堆栈的先前状态。所以,


1
您如何回收通过从大块之一中弹出东西而获得的内存?
EmilJeřábek,

好问题。快速答案是变为空闲状态的块将其内存返回到先前从中取出的空闲区域。但是,如果自该块分配内存以来,可用区域缩小了,而该块现在不与其相邻怎么办?这导致可用内存碎片化,可能会有两个以上的可用区域,这破坏了我的所有结构。
阿列克谢·凯戈罗多夫

弹出确实是这里的问题,但是Alexei的构造为Dmitri在评论中询问的问题的版本提供了一个很好的上限:如果我们要求所有弹出都发生在所有弹出之前怎么办?我想知道在这种情况下是否有比O(log N)更好的东西。
user1020406
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.