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


Answers:


701

保留2叠,我们称它们为inboxoutbox

入队

  • 将新元素推到 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();
    }

}

13
最坏情况下的时间复杂度仍为O(n)。我坚持这样说是因为我希望在那里没有学生(这听起来像是一项家庭作业/教育问题),认为这是实现队列的可接受方法。
泰勒

26
的确,单个弹出操作的最坏情况时间是O(n)(其中n是队列的当前大小)。但是,n个队列操作序列的最坏情况时间也是O(n),这给了我们摊销的恒定时间。我不会以这种方式实现队列,但是还不错。
戴夫L

1
@Tyler如果大多数情况下,堆栈都是基于数组的,那么对于单个操作,总是会遇到O(n)最坏的情况。
Thomas Ahle

2
@Tyler:检查sgi.com/tech/stl/Deque.html。双端队列“支持对元素的随机访问”。因此,双端队列和堆栈都是基于数组的。这是因为它为您提供了更好的参考位置,因此在实践中速度更快。
Thomas Ahle

13
@Newtang a)队列1,2,3 => Inbox [3,2,1] / Outbox []。b)出队。发件箱为空,因此笔芯=> Inbox [] / Outbox [1,2,3]。从发件箱中弹出,返回1 => Inbox [] / Outbox [2,3]。c)队列4,5 => Inbox [5,4] / Outbox [2,3]。d)出队。发件箱不为空,因此从发件箱中弹出,返回2 => Inbox [5,4] / Outbox [3]。这更有意义吗?
Dave L.

226

A-如何反转堆栈

要了解如何使用两个堆栈来构造队列,您应该了解如何反转透明的堆栈。记住堆栈是如何工作的,它与厨房中的碗碟非常相似。最后洗过的盘子将放在干净的烟囱顶部,这称为L ast I Ñ ˚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<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

18
如果可以的话,我会整天为+1。我无法理解固定时间如何摊销。您的插图确实清除了所有内容,尤其是将其余元素留在输出堆栈中,并且仅在清空时才重新填充的部分。
Shane McQuillan

1
这确实有助于防止我在弹出过程中遇到超时错误。我将元素放回原始堆栈中,但没有必要这样做。荣誉!
Pranit Bankar '16

2
所有评论都应以此为蓝本。
lolololol ol

4
我真的不需要解决方案,只需浏览即可。但是当我看到这样的回答时,我简直陷入了爱河。
Maverick '02

80

您甚至可以只使用一个堆栈来模拟队列。可以通过对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();
    }
}

51
@pythonquick指出,也许代码看起来很优雅,但是效率很低(O(n ** 2)插入),它仍然有两个堆栈,一个在堆中,一个在调用栈中。对于非递归算法,您始终可以使用支持递归的语言从调用堆栈中获取一个“额外”堆栈。
Antti Huima 2011年

1
@ antti.huima你能解释一下这怎么可能是二次插入?!据我了解,insert会执行n次pop和n次push操作,因此它是一种完美的线性O(n)算法。
LP_ 2014年

1
@LP_ n items使用上述数据结构需要二次时间O(n ^ 2)插入队列。总和(1 + 2 + 4 + 8 + .... + 2(n-1))~O(n^2)。我希望你明白这一点。
Ankit Kumar 2014年

1
@ antti.huima您在谈论插入函数的复杂性(您说过“ O(n 2)插入”-您可能是指“ O(n 2)填充”)。按照惯例,“复杂性插入”是时间一个的插入需要,这是在这里线性在已经存在的元素的数目。如果我们讨论插入n个项目所需的时间,则可以说哈希表具有线性插入。事实并非如此。
LP_

2
您实际上是在使用堆栈,作为堆栈。这意味着,如果堆栈中有大量物品,则可能会导致堆栈溢出-几乎就像该解决方案是为此站点设计的!
UKMonkey '16

11

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

编辑

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

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

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


不在一般情况下。Brian的答案描述了一个将分摊常量入队出队操作的队列。
Daniel Spiewak

确实如此。您的平均情况和摊销时间复杂度相同。但是默认值通常是每操作最坏的情况,这是O(n),其中n是结构的当前大小。
泰勒

1
最坏的情况也可以摊销。例如,可变的动态数组(向量)通常被认为具有恒定的插入时间,即使经常需要昂贵的调整大小和复制操作。
Daniel Spiewak

1
“最坏情况”和“摊销”是两种不同类型的时间复杂度。说“最坏的情况可以摊销”是没有意义的-如果您可以使最坏的情况=摊销,那么这将是一个重大的改进。您只会谈论最坏的情况,而不会求平均。
泰勒

我不确定O(1)最坏情况比O(1)平均情况和O(n)最坏情况的组合“严格更好”是什么意思。恒定比例因子很重要。如果数据结构包含N个项目,则可能需要在N次操作后的N毫秒后重新打包,否则每次操作需要花费一微秒的数据结构可能比每次操作需要花费一毫秒的数据结构有用得多。如果数据大小将扩展到数百万个项目(这意味着某些单个操作将花费数秒钟)。
supercat 2012年

8

令要实现的队列为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为空时才移动元素。


除了您的方法2,我没有其他解决方案。我喜欢您使用步骤中的入队和出队方法解释它的方式。
theGreenCabbage


3

C#中的解决方案

public class Queue<T> where T : class
{
    private Stack<T> input = new Stack<T>();
    private Stack<T> output = new Stack<T>();
    public void Enqueue(T t)
    {
        input.Push(t);
    }

    public T Dequeue()
    {
        if (output.Count == 0)
        {
            while (input.Count != 0)
            {
                output.Push(input.Pop());
            }
        }

        return output.Pop();
    }
}

2

队列中的两个堆栈定义为stack1stack2

排队排队的元素总是被推入stack1

出队:可弹出stack2 的顶部,因为当stack2不为空时,它是插入队列中的第一个元素。当stack2为空时,我们从stack1中弹出所有元素,并将它们逐一推入stack2中。队列中的第一个元素被推入stack1的底部。由于它位于stack2的顶部,因此可以在执行弹出和推入操作后直接将其弹出。

以下是相同的C ++示例代码:

template <typename T> class CQueue
{
public:
    CQueue(void);
    ~CQueue(void);

    void appendTail(const T& node); 
    T deleteHead();                 

private:
    stack<T> stack1;
    stack<T> stack2;
};

template<typename T> void CQueue<T>::appendTail(const T& element) {
    stack1.push(element);
} 

template<typename T> T CQueue<T>::deleteHead() {
    if(stack2.size()<= 0) {
        while(stack1.size()>0) {
            T& data = stack1.top();
            stack1.pop();
            stack2.push(data);
        }
    }


    if(stack2.size() == 0)
        throw new exception("queue is empty");


    T head = stack2.top();
    stack2.pop();


    return head;
}

该解决方案是从我的博客中借用的。我的博客网页上提供了有关逐步操作模拟的更详细分析。


2

您必须将所有内容从第一个堆栈中弹出才能获得底部元素。然后,将每个“出队”操作将它们全部放回到第二个堆栈中。


3
是的,你是对的。我想知道,您如何获得如此众多的不赞成票。我支持您的答案
Binita Bharati 2015年

令人毛骨悚然的是,这是他的最后一个答案,距那时已经十年了。
Shanu Gupta

2

对于C#开发人员,这是完整的程序:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace QueueImplimentationUsingStack
{
    class Program
    {
        public class Stack<T>
        {
            public int size;
            public Node<T> head;
            public void Push(T data)
            {
                Node<T> node = new Node<T>();
                node.data = data;
                if (head == null)
                    head = node;
                else
                {
                    node.link = head;
                    head = node;
                }
                size++;
                Display();
            }
            public Node<T> Pop()
            {
                if (head == null)
                    return null;
                else
                {
                    Node<T> temp = head;
                    //temp.link = null;
                    head = head.link;
                    size--;
                    Display();
                    return temp;
                }
            }
            public void Display()
            {
                if (size == 0)
                    Console.WriteLine("Empty");
                else
                {
                    Console.Clear();
                    Node<T> temp = head;
                    while (temp!= null)
                    {
                        Console.WriteLine(temp.data);
                        temp = temp.link;
                    }
                }
            }
        }

        public class Queue<T>
        {
            public int size;
            public Stack<T> inbox;
            public Stack<T> outbox;
            public Queue()
            {
                inbox = new Stack<T>();
                outbox = new Stack<T>();
            }
            public void EnQueue(T data)
            {
                inbox.Push(data);
                size++;
            }
            public Node<T> DeQueue()
            {
                if (outbox.size == 0)
                {
                    while (inbox.size != 0)
                    {
                        outbox.Push(inbox.Pop().data);
                    }
                }
                Node<T> temp = new Node<T>();
                if (outbox.size != 0)
                {
                    temp = outbox.Pop();
                    size--;
                }
                return temp;
            }

        }
        public class Node<T>
        {
            public T data;
            public Node<T> link;
        }

        static void Main(string[] args)
        {
            Queue<int> q = new Queue<int>();
            for (int i = 1; i <= 3; i++)
                q.EnQueue(i);
           // q.Display();
            for (int i = 1; i < 3; i++)
                q.DeQueue();
            //q.Display();
            Console.ReadKey();
        }
    }
}

2

使用堆栈实现队列的以下操作。

push(x)-将元素x推送到队列的后面。

pop()-从队列前面删除元素。

peek()-获取前元素。

empty()-返回队列是否为空。

在此处输入图片说明

class MyQueue {

  Stack<Integer> input;
  Stack<Integer> output;

  /** Initialize your data structure here. */
  public MyQueue() {
    input = new Stack<Integer>();
    output = new Stack<Integer>();
  }

  /** Push element x to the back of queue. */
  public void push(int x) {
    input.push(x);
  }

  /** Removes the element from in front of queue and returns that element. */
  public int pop() {
    peek();
    return output.pop();
  }

  /** Get the front element. */
  public int peek() {
    if(output.isEmpty()) {
        while(!input.isEmpty()) {
            output.push(input.pop());
        }
    }
    return output.peek();
  }

  /** Returns whether the queue is empty. */
  public boolean empty() {
    return input.isEmpty() && output.isEmpty();
  }
}

1
// Two stacks s1 Original and s2 as Temp one
    private Stack<Integer> s1 = new Stack<Integer>();
    private Stack<Integer> s2 = new Stack<Integer>();

    /*
     * Here we insert the data into the stack and if data all ready exist on
     * stack than we copy the entire stack s1 to s2 recursively and push the new
     * element data onto s1 and than again recursively call the s2 to pop on s1.
     * 
     * Note here we can use either way ie We can keep pushing on s1 and than
     * while popping we can remove the first element from s2 by copying
     * recursively the data and removing the first index element.
     */
    public void insert( int data )
    {
        if( s1.size() == 0 )
        {
            s1.push( data );
        }
        else
        {
            while( !s1.isEmpty() )
            {
                s2.push( s1.pop() );
            }
            s1.push( data );
            while( !s2.isEmpty() )
            {
                s1.push( s2.pop() );
            }
        }
    }

    public void remove()
    {
        if( s1.isEmpty() )
        {
            System.out.println( "Empty" );
        }
        else
        {
            s1.pop();

        }
    }

1

在Swift中使用两个堆栈实现队列:

struct Stack<Element> {
    var items = [Element]()

    var count : Int {
        return items.count
    }

    mutating func push(_ item: Element) {
        items.append(item)
    }

    mutating func pop() -> Element? {
        return items.removeLast()
    }

    func peek() -> Element? {
        return items.last
    }
}

struct Queue<Element> {
    var inStack = Stack<Element>()
    var outStack = Stack<Element>()

    mutating func enqueue(_ item: Element) {
        inStack.push(item)
    }

    mutating func dequeue() -> Element? {
        fillOutStack() 
        return outStack.pop()
    }

    mutating func peek() -> Element? {
        fillOutStack()
        return outStack.peek()
    }

    private mutating func fillOutStack() {
        if outStack.count == 0 {
            while inStack.count != 0 {
                outStack.push(inStack.pop()!)
            }
        }
    }
}

1

尽管您会获得许多与实现具有两个堆栈的队列有关的帖子:1.通过使enQueue进程的成本高得多2.或通过使deQueue进程的成本高得多

https://www.geeksforgeeks.org/queue-using-stacks/

从以上文章中发现的一种重要方法是构造仅具有堆栈数据结构和递归调用堆栈的队列。

尽管可以说实际上是在使用两个堆栈,但是理想情况下,它仅使用一个堆栈数据结构。

下面是问题的解释:

  1. 声明一个用于对数据进行排队和出队的堆栈,然后将数据推入堆栈。

  2. 而deQueueing具有一个基本条件,当堆栈的大小为1时,将弹出堆栈的元素。这将确保在deQueue递归期间不会出现堆栈溢出。

  3. 取消排队时,首先从堆栈顶部弹出数据。理想情况下,此元素将是出现在堆栈顶部的元素。现在,一旦完成,递归调用deQueue函数,然后将上面弹出的元素推回到堆栈中。

代码如下所示:

if (s1.isEmpty())
System.out.println("The Queue is empty");
        else if (s1.size() == 1)
            return s1.pop();
        else {
            int x = s1.pop();
            int result = deQueue();
            s1.push(x);
            return result;

这样,您可以使用单个堆栈数据结构和递归调用堆栈来创建队列。


1

以下是使用ES6语法的JavaScript语言解决方案。

Stack.js

//stack using array
class Stack {
  constructor() {
    this.data = [];
  }

  push(data) {
    this.data.push(data);
  }

  pop() {
    return this.data.pop();
  }

  peek() {
    return this.data[this.data.length - 1];
  }

  size(){
    return this.data.length;
  }
}

export { Stack };

QueueUsingTwoStacks.js

import { Stack } from "./Stack";

class QueueUsingTwoStacks {
  constructor() {
    this.stack1 = new Stack();
    this.stack2 = new Stack();
  }

  enqueue(data) {
    this.stack1.push(data);
  }

  dequeue() {
    //if both stacks are empty, return undefined
    if (this.stack1.size() === 0 && this.stack2.size() === 0)
      return undefined;

    //if stack2 is empty, pop all elements from stack1 to stack2 till stack1 is empty
    if (this.stack2.size() === 0) {
      while (this.stack1.size() !== 0) {
        this.stack2.push(this.stack1.pop());
      }
    }

    //pop and return the element from stack 2
    return this.stack2.pop();
  }
}

export { QueueUsingTwoStacks };

下面是用法:

index.js

import { StackUsingTwoQueues } from './StackUsingTwoQueues';

let que = new QueueUsingTwoStacks();
que.enqueue("A");
que.enqueue("B");
que.enqueue("C");

console.log(que.dequeue());  //output: "A"

这是错误的。如果您在出队后再入队,则将它们放入stack1。当您dequeue再次进入时,您将把它们移到stack2,将它们放在已经存在的位置之前。
亚历山大-恢复莫妮卡

0

我将在Go中回答这个问题,因为Go在其标准库中没有大量的集合。

由于堆栈真的很容易实现,我想我会尝试使用两个堆栈来完成双头队列。为了更好地理解我如何得出答案,我将实现分为两部分,希望第一部分更容易理解,但是还不完整。

type IntQueue struct {
    front       []int
    back        []int
}

func (q *IntQueue) PushFront(v int) {
    q.front = append(q.front, v)
}

func (q *IntQueue) Front() int {
    if len(q.front) > 0 {
        return q.front[len(q.front)-1]
    } else {
        return q.back[0]
    }
}

func (q *IntQueue) PopFront() {
    if len(q.front) > 0 {
        q.front = q.front[:len(q.front)-1]
    } else {
        q.back = q.back[1:]
    }
}

func (q *IntQueue) PushBack(v int) {
    q.back = append(q.back, v)
}

func (q *IntQueue) Back() int {
    if len(q.back) > 0 {
        return q.back[len(q.back)-1]
    } else {
        return q.front[0]
    }
}

func (q *IntQueue) PopBack() {
    if len(q.back) > 0 {
        q.back = q.back[:len(q.back)-1]
    } else {
        q.front = q.front[1:]
    }
}

基本上是两个堆栈,我们允许堆栈的底部相互操作。我还使用了STL命名约定,在该约定中,堆栈的传统push,pop,peek操作具有前/后前缀,无论它们引用队列的前还是后。

上面的代码的问题是它不能非常有效地使用内存。实际上,它会不断增长,直到您用完空间。真的很糟糕 解决方法是尽可能地重用堆栈空间的底部。我们必须引入偏移量来跟踪此情况,因为Go中的切片一旦缩小就无法在前面增长。

type IntQueue struct {
    front       []int
    frontOffset int
    back        []int
    backOffset  int
}

func (q *IntQueue) PushFront(v int) {
    if q.backOffset > 0 {
        i := q.backOffset - 1
        q.back[i] = v
        q.backOffset = i
    } else {
        q.front = append(q.front, v)
    }
}

func (q *IntQueue) Front() int {
    if len(q.front) > 0 {
        return q.front[len(q.front)-1]
    } else {
        return q.back[q.backOffset]
    }
}

func (q *IntQueue) PopFront() {
    if len(q.front) > 0 {
        q.front = q.front[:len(q.front)-1]
    } else {
        if len(q.back) > 0 {
            q.backOffset++
        } else {
            panic("Cannot pop front of empty queue.")
        }
    }
}

func (q *IntQueue) PushBack(v int) {
    if q.frontOffset > 0 {
        i := q.frontOffset - 1
        q.front[i] = v
        q.frontOffset = i
    } else {
        q.back = append(q.back, v)
    }
}

func (q *IntQueue) Back() int {
    if len(q.back) > 0 {
        return q.back[len(q.back)-1]
    } else {
        return q.front[q.frontOffset]
    }
}

func (q *IntQueue) PopBack() {
    if len(q.back) > 0 {
        q.back = q.back[:len(q.back)-1]
    } else {
        if len(q.front) > 0 {
            q.frontOffset++
        } else {
            panic("Cannot pop back of empty queue.")
        }
    }
}

它有很多小功能,但在6个功能中,有3个只是其他功能的镜像。


您在这里使用数组。我看不到您的堆栈在哪里。
melpomene '16

@melpomene好吧,如果仔细看,您会注意到我正在执行的唯一操作是添加/删除数组中的最后一个元素。换句话说,推动和弹出。出于所有目的和目的,这些都是堆栈,但使用数组实现。
约翰·莱德格伦

@melpomene实际上,那只是一半的权利,我假设两端堆栈是双倍的。我允许在某些情况下以非标准方式自下而上地修改堆栈。
约翰·莱德格伦

0

这是我使用链表的Java解决方案。

class queue<T>{
static class Node<T>{
    private T data;
    private Node<T> next;
    Node(T data){
        this.data = data;
        next = null;
    }
}
Node firstTop;
Node secondTop;

void push(T data){
    Node temp = new Node(data);
    temp.next = firstTop;
    firstTop = temp;
}

void pop(){
    if(firstTop == null){
        return;
    }
    Node temp = firstTop;
    while(temp != null){
        Node temp1 = new Node(temp.data);
        temp1.next = secondTop;
        secondTop = temp1;
        temp = temp.next;
    }
    secondTop = secondTop.next;
    firstTop = null;
    while(secondTop != null){
        Node temp3 = new Node(secondTop.data);
        temp3.next = firstTop;
        firstTop = temp3;
        secondTop = secondTop.next;
    }
}

}

注意:在这种情况下,弹出操作非常耗时。所以我不建议使用两个堆栈来创建队列。


0

使用O(1) dequeue(),与pythonquick的答案相同:

// time: O(n), space: O(n)
enqueue(x):
    if stack.isEmpty():
        stack.push(x)
        return
    temp = stack.pop()
    enqueue(x)
    stack.push(temp)

// time: O(1)
x dequeue():
    return stack.pop()

with O(1) enqueue()(此帖子中未提及此答案),它也使用回溯来冒泡并返回最底端的项目。

// O(1)
enqueue(x):
    stack.push(x)

// time: O(n), space: O(n)
x dequeue():
    temp = stack.pop()
    if stack.isEmpty():
        x = temp
    else:
        x = dequeue()
        stack.push(temp)
    return x

显然,这是一个很好的编码练习,因为它效率低下,但是却很优雅。


0

**简易的JS解决方案**

  • 注意:我从其他人那里收集了意见

/*

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.

*/
class myQueue {
    constructor() {
        this.stack1 = [];
        this.stack2 = [];
    }

    push(item) {
        this.stack1.push(item)
    }

    remove() {
        if (this.stack1.length == 0 && this.stack2.length == 0) {
            return "Stack are empty"
        }

        if (this.stack2.length == 0) {

            while (this.stack1.length != 0) {
                this.stack2.push(this.stack1.pop())
            }
        }
        return this.stack2.pop()
    }


    peek() {
        if (this.stack2.length == 0 && this.stack1.length == 0) {
            return 'Empty list'
        }

        if (this.stack2.length == 0) {
            while (this.stack1.length != 0) {
                this.stack2.push(this.stack1.pop())
            }
        }

        return this.stack2[0]
    }

    isEmpty() {
        return this.stack2.length === 0 && this.stack1.length === 0;
    }

}

const q = new myQueue();
q.push(1);
q.push(2);
q.push(3);
q.remove()

console.log(q)


-1
public class QueueUsingStacks<T>
{
    private LinkedListStack<T> stack1;
    private LinkedListStack<T> stack2;

    public QueueUsingStacks()
    {
        stack1=new LinkedListStack<T>();
        stack2 = new LinkedListStack<T>();

    }
    public void Copy(LinkedListStack<T> source,LinkedListStack<T> dest )
    {
        while(source.Head!=null)
        {
            dest.Push(source.Head.Data);
            source.Head = source.Head.Next;
        }
    }
    public void Enqueue(T entry)
    {

       stack1.Push(entry);
    }
    public T Dequeue()
    {
        T obj;
        if (stack2 != null)
        {
            Copy(stack1, stack2);
             obj = stack2.Pop();
            Copy(stack2, stack1);
        }
        else
        {
            throw new Exception("Stack is empty");
        }
        return obj;
    }

    public void Display()
    {
        stack1.Display();
    }


}

对于每个入队操作,我们将其添加到stack1的顶部。对于每个出队,我们将stack1的内容清空到stack2中,然后删除堆栈顶部的元素。出队的时间复杂度为O(n),因为我们必须将stack1复制到stack2。入队的时间复杂度与常规堆栈相同


该代码效率低下(不必要的复制)并被破坏:if (stack2 != null)始终为true,因为stack2在构造函数中已实例化。
melpomene'8

-2

使用两个java.util.Stack对象的队列实现:

public final class QueueUsingStacks<E> {

        private final Stack<E> iStack = new Stack<>();
        private final Stack<E> oStack = new Stack<>();

        public void enqueue(E e) {
            iStack.push(e);
        }

        public E dequeue() {
            if (oStack.isEmpty()) {
                if (iStack.isEmpty()) {
                    throw new NoSuchElementException("No elements present in Queue");
                }
                while (!iStack.isEmpty()) {
                    oStack.push(iStack.pop());
                }
            }
            return oStack.pop();
        }

        public boolean isEmpty() {
            if (oStack.isEmpty() && iStack.isEmpty()) {
                return true;
            }
            return false;
        }

        public int size() {
            return iStack.size() + oStack.size();
        }

}

3
该代码在功能上与Dave L的答案相同。它没有添加任何新内容,甚至也没有解释。
melpomene'8

它添加了isEmpty()和size()方法以及基本的异常处理。我将进行编辑以添加说明。
realPK

1
没有人要求这些额外的方法,它们很简单(每行一行):return inbox.isEmpty() && outbox.isEmpty()return inbox.size() + outbox.size()。当您从空队列中出队时,Dave L.的代码已经引发了异常。最初的问题甚至与Java无关。它通常是关于数据结构/算法的。Java实现只是一个附加说明。
melpomene '16

1
对于希望了解如何从两个堆栈中构建队列的人来说,这是一个很好的资源,这些图绝对比阅读Dave的答案对我有更多帮助。
凯末尔·泰泽

@melpomene:它不是关于琐碎的方法而是需要的。Java中的队列接口因为需要它们而从Collection接口扩展了这些方法。
realPK
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.