Stack Using Two Queues in Java

2025年5月10日 | 阅读 4 分钟

堆栈 是一种线性数据结构,它实现了后进先出(LIFO)方法,因此最后添加的元素最先被移除。由于它们按照先进先出原则运行,因此需要使用两个FIFO队列来实现 LIFO 堆栈。将使用 FIFO 过程来复制 LIFO (后进先出)功能。

这个问题是算法设计的一个绝佳练习,需要高效的入队和出队操作来确保正常运行。挑战在于决定是使推(push)操作还是弹(pop)操作成本高昂,从而根据预期的使用模式来优化性能。

有两种方法:

  1. 使推(插入)操作成本高昂:它通过在队列之间战略性地转移元素来确保顶元素保留在前面。这样可以有效地访问最近插入的元素,从而使用队列来保持堆栈行为。
  2. 使弹出(移除)操作成本高昂:它在两个队列之间转移元素以模拟堆栈行为,确保后进先出(LIFO)顺序。此移动有助于仅使用队列操作来维护正确的推和弹操作。

此实现适用于我们仅限于使用队列操作并需要堆栈功能的场景。

算法

我们可以通过使推或弹操作成本高昂的方式,使用两个队列(q1 和 q2)来实现堆栈。下面是一种推操作成本高昂,而弹和顶操作高效的方法。

  1. 初始化两个队列
    • 维护两个队列:q1(主队列)和 q2(临时队列)。
  2. 推操作(成本高昂)
    • 将新元素插入 q2。
    • 将 q1 中的所有元素移至 q2。
    • 交换 q1 和 q2 以保持顺序。
  3. 弹出操作(高效)
    • 从 q1 中移除第一个元素。
  4. 顶操作(高效)
    • 返回 q1 的第一个元素。
  5. IsEmpty 操作
    • 如果 q1 为空,则返回 true,否则返回 false。

输出

 
Top element: 30
Popped element: 30
Popped element: 20
Stack empty: false
Popped element: 10
Stack empty: true   

解释

这个Java程序使用两个队列(q1 和 q2)实现了一个堆栈。推操作的成本较高,因为新元素首先添加到 q2,然后从 q1 移动到 q2 的所有元素在交换队列之前。弹和顶操作的成本较低,因为元素直接从 q1 出队。这种方法可确保 LIFO 行为,同时保持高效的检索操作。

关键观察

推操作成本高昂:添加新元素并重新排列顺序,使时间复杂度为 O(n)。

弹出和顶操作高效:由于最后插入的元素始终位于前面,因此弹出和顶操作需要 O(1) 时间。

使用两个队列:交换队列有助于在不增加额外存储的情况下保持顺序。

替代方法:如果弹出操作成本高昂,则仅在弹出时移动元素,使推操作为 O(1),弹出操作为 O(n)。

暴力法与最佳解决方案

暴力破解法

1. 使用数组

  • 在末尾插入元素(O(1))。
  • 从末尾移除元素(O(1))。
  • 问题在于使用 FIFO 操作来维护 LIFO。

最佳方法(使用两个队列)

  • 高效使用队列操作。
  • 确保 LIFO 行为,推操作为 O(n),弹出/顶操作为 O(1)。
  • 使用队列交换高效地维护顺序。

结论

使用两个队列实现堆栈是数据结构转换中的一个有趣问题。通过巧妙地利用入队和出队操作,我们可以在仅使用队列的情况下模拟堆栈行为。此方法在硬件限制或仅允许队列操作的特定编程约束的情况下很有用。

此方法的效率取决于推操作或弹出操作哪个成本高昂。如果需要频繁的弹出操作,则使推操作成本高昂(如我们的实现)是有益的。但是,如果插入操作更频繁,则可能首选成本高昂的弹出操作。这些实现之间的选择取决于具体用例的需求。


下一个主题Java 中的基数