【问题标题】:How to implement a queue using two stacks?如何使用两个堆栈实现一个队列?
【发布时间】:2010-09-09 07:28:07
【问题描述】:

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

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

【问题讨论】:

  • 为了一笑而过,将两个堆栈实现在一个数组中,一个从两端朝向彼此增长。将栈顶序列与队列的直接数组实现进行比较。

标签: algorithm data-structures stack queue


【解决方案1】:

不过,时间复杂度会更糟。一个好的队列实现在恒定时间内完成所有事情。

编辑

不知道为什么我的回答在这里被否决了。如果我们编程,我们关心时间复杂度,并且使用两个标准堆栈来制作队列是低效的。这是一个非常有效和相关的观点。如果其他人觉得有必要对此投反对票,我很想知道原因。

更多细节:关于为什么使用两个堆栈比仅使用一个队列更糟糕:如果您使用两个堆栈,并且有人在发件箱为空时调用 dequeue,您需要线性时间才能到达收件箱的底部(正如您在 Dave 的代码中看到的那样)。

您可以将队列实现为单链表(每个元素指向下一个插入的元素),保留一个指向最后插入元素的额外指针以进行推送(或使其成为循环列表)。在这个数据结构上实现队列和出队很容易在常数时间内完成。这是最坏情况下的常数时间,没有摊销。而且,正如 cmets 似乎要求澄清的那样,最坏情况下的常数时间严格优于摊销常数时间。

【讨论】:

  • 一般情况下不会。 Brian 的回答描述了一个队列,该队列将摊销常量入队 出队操作。
  • 确实如此。您的平均案例和摊销时间复杂度相同。但默认值通常是每个操作的最坏情况,这是 O(n),其中 n 是结构的当前大小。
  • 最坏情况也可以摊销。例如,可变动态数组(向量)通常被认为具有恒定的插入时间,即使经常需要进行昂贵的调整大小和复制操作。
  • “最坏情况”和“摊销”是两种不同类型的时间复杂度。说“最坏情况可以摊销”是没有意义的——如果你可以让最坏情况=摊销,那么这将是一个重大的改进;你只会谈论最坏的情况,没有平均。
  • 我不确定你所说的 O(1) 最坏情况比 O(1) 平均情况和 O(n) 最坏情况的组合“严格更好”是什么意思。恒定比例因子很重要。一个数据结构,如果它包含 N 个项目,可能需要在 N 次操作后以 N 微秒的时间重新打包,否则每次操作需要 1 微秒,可能比每次操作需要 1 毫秒的数据结构有用得多,甚至如果数据大小将扩展到数百万个项目(这意味着某些单独的操作将需要几秒钟)。
【解决方案2】:

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

【讨论】:

  • 是的,你是对的。我想知道,你是怎么得到这么多反对票的。我赞成你的回答
【解决方案3】:

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

}

【讨论】:

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

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

向队列中插入新元素时,原理不变:

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

仅使用一个 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();
    }
}

【讨论】:

  • 也许代码看起来很优雅,但效率很低(O(n**2) 插入),它仍然有两个堆栈,一个在堆中,一个在调用堆栈中,正如@pythonquick 指出的那样出去。对于非递归算法,您始终可以从支持递归的语言的调用堆栈中获取一个“额外”堆栈。
  • @antti.huima 你能解释一下这怎么可能是一个二次插入?!据我了解,insert 执行 n 次 pop 和 n 次 push 操作,因此它是一个完美的线性 O(n) 算法。
  • @LP_ 使用上述数据结构在队列中插入n items 需要二次时间 O(n^2)。 (1 + 2 + 4 + 8 + .... + 2(n-1)) 的总和为 ~O(n^2)。我希望你明白这一点。
  • @antti.huima 您说的是插入函数的复杂性(您说“O(n2) 插入”——您的意思可能是“O(n2 ) 充满”)。 按照惯例,“插入复杂度”是一个插入所花费的时间,这里与已经存在的元素数量成线性关系。如果我们谈到插入 n 项所需的时间,我们会说哈希表具有线性插入。事实并非如此。
  • 您实际上是在将堆栈用作堆栈。这意味着如果堆栈中有大量项目,您最终可能会出现堆栈溢出 - 这几乎就像为该站点设计的解决方案一样!
【解决方案5】:
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();
    }


}

对于每个入队操作,我们添加到堆栈的顶部1。对于每个出队,我们将stack1的内容清空到stack2中,并移除栈顶的元素。出队的时间复杂度为O(n),因为我们必须将stack1复制到stack2。 enqueue的时间复杂度和普通的stack一样

【讨论】:

  • 此代码效率低下(不必要的复制)且损坏:if (stack2 != null) 始终为真,因为stack2 在构造函数中被实例化。
【解决方案6】:

队列中的两个栈定义为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;
}

这个解决方案是从my blog借来的。更详细的分步操作模拟分析可在我的博客网页中找到。

【讨论】:

    【解决方案7】:

    令要实现的队列为 q,用于实现 q 的堆栈为 stack1 和 stack2。

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

    方法 1(通过使 enQueue 操作代价高昂)

    此方法确保新进入的元素始终位于堆栈 1 的顶部,因此 deQueue 操作只是从堆栈 1 中弹出。要将元素放在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 之外,我没有理解任何解决方案。我喜欢你用 enqueue 和 dequeue 方法和步骤来解释它的方式。
    【解决方案8】:
    // 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();
    
            }
        }
    

    【讨论】:

    • 如果没有描述必要的操作次数,这个答案是没有用的。
    【解决方案9】:

    我将在 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 好的,如果您仔细观察,您会注意到我正在执行的唯一操作是添加/删除数组中的最后一个元素。换句话说,推动和弹出。出于所有意图和目的,这些都是堆栈,但使用数组实现。
    • @melpomene 实际上,这只是对了一半,我假设双端堆栈。我允许在某些条件下以非标准方式从下到上修改堆栈。
    【解决方案10】:

    使用两个 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();
            }
    
    }
    

    【讨论】:

    • 此代码在功能上与 Dave L 的答案相同。它没有添加任何新内容,甚至没有添加任何解释。
    • 它添加了 isEmpty() 和 size() 方法以及基本的异常处理。我将编辑添加解释。
    • 没有人要求这些额外的方法,而且它们很简单(每行一行):分别为 return inbox.isEmpty() &amp;&amp; outbox.isEmpty()return inbox.size() + outbox.size()。当您从空队列中出列时,Dave L. 的代码已经抛出异常。最初的问题甚至与 Java 无关。它一般是关于数据结构/算法的。 Java 实现只是一个补充说明。
    • 对于希望了解如何从两个堆栈构建队列的人来说,这是一个很好的资源,这些图表绝对比阅读 Dave 的答案对我的帮助更大。
    • @KemalTezerDilsiz 什么图表?您是否评论了错误的答案?
    【解决方案11】:

    A - 如何反转堆栈

    要了解如何使用两个堆栈构建队列,您应该清楚地了解如何反转堆栈。记住堆栈的工作原理,它与厨房里的盘子非常相似。最后一个洗过的盘子将在干净堆栈的顶部,称为 Last In First Out (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} 分别排入队列。整数将被推送到位于左侧的 Input StackStack #1);

    那么如果我们执行出队操作会发生什么?每当执行出队操作时,队列将检查输出堆栈是否为空(请参见上面的伪代码)如果输出堆栈为空,则将在输出上提取输入堆栈,因此元素输入堆栈将被反转。在返回值之前,队列的状态如下:

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

    现在Output Stack不是空的,如果我们执行dequeue操作,只会从Output Stack中弹出3个。然后状态将如下所示;

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

    不难看出,两个出队操作的输出将是{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
    

    【讨论】:

    • 如果可以的话,我会整天为此 +1。我无法理解它是如何摊销恒定时间的。你的插图真的把事情搞清楚了,尤其是把剩余元素留在输出堆栈上的部分,只有在它清空时才重新填充。
    • 这确实有助于防止我在弹出期间遇到超时错误。我将元素放回原始堆栈中,但没有必要这样做。赞一个!
    • 所有的cmets都应该仿照这个。
    • 我真的不需要解决方案,只是浏览......但是当我看到这样的答案时,我简直爱上了..很好的答案!!!
    【解决方案12】:

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

    【讨论】:

      【解决方案13】:

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

      【讨论】:

        【解决方案14】:

        这是我在 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;
                }
            }
            
        }
        

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

        【讨论】:

        • 队列操作在哪里,比如enqueue(T value)T dequeue()?是否需要在pop() 中实例化Nodes?如果没有描述必要的操作数量,这个答案是没有用的。
        【解决方案15】:

        O(1)dequeue(),和pythonquick的answer一样:

        // 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()
        

        使用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
        

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

        【讨论】:

          【解决方案16】:

          在 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()!)
                      }
                  }
              }
          }
          

          【讨论】:

            【解决方案17】:

            虽然您会收到很多与实现具有两个堆栈的队列相关的帖子: 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;
            

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

            【讨论】:

            • Below is the explanation of the problem promises, promises - 1., 2., 和 3. 在描述一个解决方案时看起来很努力。为什么s1 既没有s0 也没有s2?在return 后面有一个else。有一个空缺 { 没有匹配的 }
            【解决方案18】:

            以下是使用 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,将它们放在已经存在的位置之前。
            • @Alexander: you'll move them items into stack2, putting them ahead of what was already there 当且仅当this.stack2.size() === 0,将这些元素放在前面 - nothing
            • 哈哈我3年前写的(差不多到现在),现在我不明白我的意思了
            【解决方案19】:

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

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

            【讨论】:

              【解决方案20】:

              **简单的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)

              【讨论】:

                【解决方案21】:

                我的 PHP 解决方案

                <?php
                $_fp = fopen("php://stdin", "r");
                /* Enter your code here. Read input from STDIN. Print output to STDOUT */
                    $queue = array();
                    $count = 0;
                    while($line = fgets($_fp)) {
                        if($count == 0) {
                            $noOfElement = $line;
                            $count++;
                            continue;
                        }
                        $action = explode(" ",$line);
                        $case = $action[0];
                        switch($case) {
                            case 1:
                                $enqueueValue = $action[1];
                                array_push($queue, $enqueueValue);
                                break;
                            case 2:
                                array_shift($queue);
                                break;
                            case 3:
                                $show = reset($queue);
                                print_r($show);
                                break;
                            default:
                                break;
                        }
                    }
                ?>
                

                【讨论】:

                • 我看到一个没有评论。我没有看到一个堆栈。这篇文章应该回答什么问题?
                猜你喜欢
                • 2014-04-21
                • 2010-10-15
                • 2017-12-21
                • 2012-09-02
                • 2016-03-10
                • 2014-12-23
                • 1970-01-01
                • 2018-06-14
                • 1970-01-01
                相关资源
                最近更新 更多