当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《如何使用两个堆栈实现队列?》经验,为你挑选了5个好方法。

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

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



1> Dave L...:

保持2堆,让我们给他们打电话inboxoutbox.

入队:

将新元素推入 inbox

出队:

如果outbox为空,则通过弹出每个元素inbox并将其推入来重新填充outbox

弹出并返回顶部元素 outbox

使用此方法,每个元素将在每个堆栈中恰好一次 - 意味着每个元素将被按两次并弹出两次,从而给出了分摊的常量时间操作.

这是Java中的一个实现:

public class Queue
{

    private Stack inbox = new Stack();
    private Stack outbox = new Stack();

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

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

}


确实,单个弹出操作的最坏情况时间是O(n)(其中n是队列的当前大小).但是,n个队列操作序列的最坏情况时间也是O(n),给出了分摊的常量时间.我不会以这种方式实现队列,但它并没有那么糟糕.
@Newtang a)队列1,2,3 =>**收件箱[3,2,1] /发件箱[]**.b)出列队.发件箱为空,所以重新填充=>**收件箱[] /发件箱[1,2,3]**.从发件箱弹出,返回1 =>**收件箱[] /发件箱[2,3]**.c)队列4,5 =>**收件箱[5,4] /发件箱[2,3]**.d)出列队.发件箱不为空,因此从发件箱弹出,返回2 =>**收件箱[5,4] /发件箱[3]**.那更有意义吗?
最坏情况下的时间复杂度仍为O(n).我坚持这样说是因为我希望没有学生(这听起来像是家庭作业/教育问题)认为这是实施队列的可接受方式.
@Tyler:检查http://www.sgi.com/tech/stl/Deque.html。双端队列“支持对元素的随机访问”。因此,双端队列和堆栈都是基于数组的。这是因为它为您提供了更好的参考位置,因此在实践中速度更快。

2> Levent Divil..:
A - 如何反转堆栈

要了解如何使用两个堆栈构造队列,您应该了解如何反转堆栈清晰.记住堆栈是如何工作的,它非常类似于厨房的碟子堆栈.最后洗涤盘将在清洁堆栈的顶部,其被称作大号 AST Ñ ˚F开始步骤ö在计算机科学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 {

    // inner generic Node class
    private class Node {
        T data;
        Node next;

        public Node(T data) {
            this.data = data;
        }
    }

    private Node head;
    private int size;

    public void push(T e) {
        Node 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 temp = head; temp != null; temp = temp.next)
                System.out.printf("%s ", temp.data);

        System.out.printf("\n");
    }
}
C - 2)MyQueue类:使用两个堆栈的队列实现
public class MyQueue {

    private MyStack inputStack;      // for enqueue
    private MyStack 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 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


如果可以的话,我会整整+1.我无法理解它是如何按时间摊销的.你的插图确实清理了一些东西,尤其是将剩余元素留在输出堆栈上的部分,只有当它清空时才重新填充.
我真的不需要这个解决方案,只是浏览...但是当我看到这样的答案时,我只是坠入爱河..很棒的答案!!!
所有评论都应以此为蓝本。

3> pythonquick..:

您甚至可以仅使用一个堆栈来模拟队列.第二个(临时)堆栈可以通过对insert方法的递归调用的调用堆栈来模拟.

将新元素插入队列时,原则保持不变:

您需要将元素从一个堆栈传输到另一个临时堆栈,以反转它们的顺序.

然后将要插入的新元素推入临时堆栈

然后将元素传回原始堆栈

新元素将位于堆栈的底部,最旧的元素位于顶部(首先被弹出)

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

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

    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();
    }
}


也许代码看起来很优雅但效率非常低(O(n**2)insert)并且它仍然有两个堆栈,一个在堆中,一个在调用堆栈中,正如@pythonquick指出的那样.对于非递归算法,您总是可以在支持递归的语言中从调用堆栈中获取一个"额外"堆栈.
你实际上是在堆栈中使用堆栈.这意味着如果堆栈中有大量项目,最终可能会出现堆栈溢出 - 这几乎就像解决方案是为此站点设计的!

4> Tyler..:

但是,时间的复杂性会更糟.良好的队列实现可以在恒定时间内完成所有任

编辑

不知道为什么我的答案在这里被低估了.如果我们编程,我们关心时间复杂度,并使用两个标准堆栈来制作队列是低效的.这是一个非常有效和相关的观点.如果其他人觉得需要更多地投票,我会有兴趣知道原因.

更详细一点:为什么使用两个堆栈比一个队列更糟糕:如果你使用两个堆栈,而有人在发件箱为空时调用出队,你需要线性时间到达收件箱的底部(如你所见)在戴夫的代码中).

您可以将队列实现为单链接列表(每个元素指向下一个插入的元素),保留指向最后插入元素的额外指针以进行推送(或使其成为循环列表).在此数据结构上实现队列和出列很容易在恒定时间内完成.这是最坏情况下的常数时间,而非摊销.而且,由于评论似乎要求澄清,最坏情况下的常数时间绝对优于摊销的常数时间.



5> Rahul Gandhi..:

让队列实现为q,用于实现q的栈是stack1和stack2.

q可以通过两种方式实现:

方法1(通过使enQueue操作成本高昂)

此方法确保新输入的元素始终位于堆栈1的顶部,因此deQueue操作只是从stack1弹出.要将元素放在stack1的顶部,使用stack2.

enQueue(q, x)
1) While stack1 is not empty, push everything from stack1 to stack2.
2) Push x to stack1 (assuming size of stacks is unlimited).
3) Push everything back to stack1.
deQueue(q)
1) If stack1 is empty then error
2) Pop an item from stack1 and return it.

方法2(通过使deQueue操作成本高昂)

在此方法中,在队列操作中,新元素输入stack1的顶部.在去队列操作中,如果stack2为空,则将所有元素移动到stack2,最后返回stack2的顶部.

enQueue(q,  x)
 1) Push x to stack1 (assuming size of stacks is unlimited).

deQueue(q)
 1) If both stacks are empty then error.
 2) If stack2 is empty
   While stack1 is not empty, push everything from stack1 to stack2.
 3) Pop the element from stack2 and return it.

方法2肯定比​​方法1好.方法1在enQueue操作中移动所有元素两次,而方法2(在deQueue操作中)移动元素一次并仅在stack2为空时移动元素.

推荐阅读
谢谢巷议
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有