如何使用两个堆栈实现队列?

时间:2020-03-05 18:55:05  来源:igfitidea点击:

假设我们有两个堆栈,没有其他临时变量。

是否可以仅使用两个堆栈来"构造"队列数据结构?

解决方案

回答

但是,时间复杂性会更糟。一个好的队列实现可以在固定时间内完成所有工作。

编辑

不知道为什么我的答案在这里被低估了。如果我们进行编程,我们会关心时间的复杂性,并且使用两个标准堆栈来创建队列效率很低。这是非常有效和相关的一点。如果其他人觉得有必要对此进行更多投票,我很想知道为什么。

更多细节:关于为什么使用两个堆栈比仅使用队列更糟糕的原因:如果使用两个堆栈,并且发件箱为空时有人呼叫出队,则需要线性时间才能到达收件箱的底部(如我们所见在Dave的代码中)。

我们可以将队列实现为单链接列表(每个元素都指向下一个插入的元素),并保留指向最后插入的元素的额外指针以进行推送(或者使其成为循环列表)。在恒定的时间内非常容易在此数据结构上实现队列和出队。那是最坏的固定时间,没有摊销。而且,正如评论所要求的那样,最坏情况下的恒定时间严格比摊销的恒定时间要好。

回答

我们必须将所有内容从第一个堆栈中弹出,以获取底部元素。然后将它们全部放回第二个堆栈,以进行每个"出队"操作。

回答

保留2个堆栈,我们将其称为"收件箱"和"发件箱"。

入队:

  • 将新元素推入"收件箱"

出队:

  • 如果"发件箱"为空,则通过从"收件箱"中弹出每个元素并将其推到"发件箱"中来重新填充
  • 弹出并从"发件箱"中返回顶部元素

使用此方法,每个元素将在每个堆栈中恰好出现一次,这意味着每个元素将被压入两次并弹出两次,从而提供了摊销的固定时间操作。

这是Java的实现:

public class Queue<E>
{

    private Stack<E> inbox = new Stack<E>();
    private Stack<E> outbox = new Stack<E>();

    public void queue(E item) {
        inbox.push(item);
    }

    public E dequeue() {
        if (outbox.isEmpty()) {
            while (!inbox.isEmpty()) {
               outbox.push(inbox.pop());
            }
        }
        return outbox.pop();
    }

}

回答

我们甚至可以只使用一个堆栈来模拟队列。可以通过对insert方法进行递归调用的调用堆栈来模拟第二个(临时)堆栈。

在队列中插入新元素时,原理保持不变:

  • 我们需要将元素从一个堆栈转移到另一个临时堆栈,以颠倒它们的顺序。
  • 然后将要插入的新元素推入临时堆栈
  • 然后将元素转移回原始堆栈
  • 新元素将位于堆栈的底部,而最旧的元素将位于堆栈的顶部(首先弹出)

仅使用一个Stack的Queue类如下:

public class SimulatedQueue<E> {
    private java.util.Stack<E> stack = new java.util.Stack<E>();

    public void insert(E elem) {
        if (!stack.empty()) {
            E topElem = stack.pop();
            insert(elem);
            stack.push(topElem);
        }
        else
            stack.push(elem);
    }

    public E remove() {
        return stack.pop();
    }
}