如何使用两个堆栈实现队列?
假设我们有两个堆栈,没有其他临时变量。
是否可以仅使用两个堆栈“构造”队列数据结构?
  保留2个堆栈,我们称他们为inbox和发件outbox 。 
排队 :
inbox 出队 :
  如果发件outbox为空,请通过从inbox弹出每个元素并将其推送到发件outbox重新填充 
  弹出并返回发件outbox的顶部元素 
使用这种方法,每个元素将在每个堆栈中只有一次 - 这意味着每个元素将被按两次并弹出两次,从而提供分摊的恒定时间操作。
以下是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();
    }
}
A - 如何反转堆栈
要了解如何使用两个堆栈构建队列,您应该了解如何将透明堆栈反转。 请记住堆叠是如何工作的,它与厨房的盘子非常相似。 最后洗涤盘将在清洁堆栈的顶部,其被称作计算机科学大号 AST I N 开始步骤øUT(LIFO)。
让我们想像我们的堆栈像一个瓶子如下;
如果我们分别推入整数1,2,3,那么3将位于堆栈顶部。 因为1会先被推动,然后2会被放在1的顶部。最后,3将被放在堆栈的顶部,并且我们的堆栈的最新状态将如下所示;
现在我们将我们的堆栈表示为一个瓶子,其值为3,2,1。 我们想要反转堆栈,以使堆栈的顶层元素为1,堆栈的底层元素为3.我们可以做什么? 我们可以拿起瓶子并倒过来,这样所有的数值都应该颠倒过来?

是的,我们可以做到这一点,但这是一个瓶子。 为了做同样的过程,我们需要第二个堆栈,它将以相反的顺序存储第一个堆栈元素。 让我们把我们填充的堆栈放到左边,我们新的空堆栈放在右边。 为了颠倒这些元素的顺序,我们将弹出左堆栈中的每个元素,并将它们推送到正确的堆栈。 你可以看到下面的图片会发生什么,

所以我们知道如何反转堆栈。
B - 使用两个栈作为队列
  在前面的部分中,我已经解释了如何颠倒堆栈元素的顺序。  这很重要,因为如果我们将元素推送到堆栈并将其弹出,则输出将与队列完全相反。  以一个例子为例,让我们将整数数组{1, 2, 3, 4, 5}推入堆栈。  如果我们弹出元素并打印它们直到堆栈为空,我们将按照推送顺序的相反顺序获取数组,这将是{5, 4, 3, 2, 1}请记住,对于相同的输入,如果我们将队列出队,直到队列为空,输出将为{1, 2, 3, 4, 5} 。  所以很显然,对于相同的元素输入顺序,队列的输出与堆栈的输出完全相反。  由于我们知道如何使用额外的堆栈来反转堆栈,我们可以使用两个堆栈来构建一个队列。 
  我们的队列模型将由两个堆栈组成。  一个堆栈将用于enqueue操作(堆栈#1在左边,将被称为输入堆栈),另一个堆栈将用于dequeue操作(堆栈#2在右边,将被称为输出堆栈)。  看看下面的图片; 

我们的伪代码如下所示;
入队操作
Push every input element to the Input Stack
出队操作
If ( Output Stack is Empty)
    pop every element in the Input Stack
    and push them to the Output Stack until Input Stack is Empty
pop from Output Stack
  让我们分别排列整数{1, 2, 3} 。  整数将被压入位于左侧的输入堆栈 ( 堆栈#1 ); 

那么如果我们执行一个出队操作会发生什么? 无论何时执行出队操作,队列都将检查输出堆栈是否为空(请参阅上面的伪代码)。如果输出堆栈为空,则将在输出中提取输入堆栈,以便元素输入堆栈将被颠倒。 在返回值之前,队列的状态如下所示;

  检查输出堆栈(堆栈#2)中元素的顺序。  很显然,我们可以从输出堆栈弹出元素,以便输出与从队列中出队时相同。  因此,如果我们执行两个出队操作,首先我们将分别得到{1, 2} 。  然后元素3将是输出堆栈的唯一元素,并且输入堆栈将为空。  如果我们排队元素4和5,那么队列的状态将如下; 

现在输出堆栈不是空的,如果我们执行出队操作,则只有3个会从输出堆栈弹出。 那么国家将被视为如下。

同样,如果我们再执行两次出队操作,则在第一次出队操作时,队列将检查输出堆栈是否为空,这是真的。 然后弹出输入堆栈的元素并将它们推送到输出堆栈,直到输入堆栈为空,那么队列的状态如下所示;

  很容易看到,两个出队操作的输出将是{4, 5} 
C - 构造两个栈的队列的实现
这是Java中的一个实现。 我不打算使用Stack的现有实现,因此这里的示例将重新发明轮子;
C - 1)MyStack类:简单堆栈实现
public class MyStack<T> {
    // inner generic Node class
    private class Node<T> {
        T data;
        Node<T> next;
        public Node(T data) {
            this.data = data;
        }
    }
    private Node<T> head;
    private int size;
    public void push(T e) {
        Node<T> newElem = new Node(e);
        if(head == null) {
            head = newElem;
        } else {
            newElem.next = head;
            head = newElem;     // new elem on the top of the stack
        }
        size++;
    }
    public T pop() {
        if(head == null)
            return null;
        T elem = head.data;
        head = head.next;   // top of the stack is head.next
        size--;
        return elem;
    }
    public int size() {
        return size;
    }
    public boolean isEmpty() {
        return size == 0;
    }
    public void printStack() {
        System.out.print("Stack: ");
        if(size == 0)
            System.out.print("Empty !");
        else
            for(Node<T> temp = head; temp != null; temp = temp.next)
                System.out.printf("%s ", temp.data);
        System.out.printf("n");
    }
}
C - 2)MyQueue类:使用两个堆栈实现队列
public class MyQueue<T> {
    private MyStack<T> inputStack;      // for enqueue
    private MyStack<T> outputStack;     // for dequeue
    private int size;
    public MyQueue() {
        inputStack = new MyStack<>();
        outputStack = new MyStack<>();
    }
    public void enqueue(T e) {
        inputStack.push(e);
        size++;
    }
    public T dequeue() {
        // fill out all the Input if output stack is empty
        if(outputStack.isEmpty())
            while(!inputStack.isEmpty())
                outputStack.push(inputStack.pop());
        T temp = null;
        if(!outputStack.isEmpty()) {
            temp = outputStack.pop();
            size--;
        }
        return temp;
    }
    public int size() {
        return size;
    }
    public boolean isEmpty() {
        return size == 0;
    }
}
C - 3)演示代码
public class TestMyQueue {
    public static void main(String[] args) {
        MyQueue<Integer> queue = new MyQueue<>();
        // enqueue integers 1..3
        for(int i = 1; i <= 3; i++)
            queue.enqueue(i);
        // execute 2 dequeue operations 
        for(int i = 0; i < 2; i++)
            System.out.println("Dequeued: " + queue.dequeue());
        // enqueue integers 4..5
        for(int i = 4; i <= 5; i++)
            queue.enqueue(i);
        // dequeue the rest
        while(!queue.isEmpty())
            System.out.println("Dequeued: " + queue.dequeue());
    }
}
C - 4)样本输出
Dequeued: 1
Dequeued: 2
Dequeued: 3
Dequeued: 4
Dequeued: 5
你甚至可以使用一个堆栈模拟一个队列。 第二个(临时)堆栈可以通过递归调用insert方法的调用堆栈模拟。
将新元素插入队列时,原理保持不变:
一个Queue类只使用一个Stack,如下所示:
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();
    }
}
上一篇: How to implement a queue using two stacks?
下一篇: What's the difference between Program Fixpoint and Function in Coq?
