文章中如果有图看不到,可以点这里去 csdn 看看。从那边导过来的,文章太多,没法一篇篇修改好。

【数据结构变种】Java 用栈实现队列

要用栈实现队列,关键在于模拟队列的先进先出(FIFO)特性,而栈是后进先出(LIFO)的。一个常见且高效的方法是使用两个栈来协作:一个栈(称为“输入栈”)负责处理入队(enqueue)操作,另一个栈(称为“输出栈”)负责处理出队(dequeue)和查看队头(peek)操作。下面我将详细解释实现原理、步骤,并提供一个代码示例(使用 Python,因为简单易懂)。

实现原理

  • 队列的核心操作
    • enqueue(item):在队尾添加元素。
    • dequeue():移除并返回队头元素。
    • peek():返回队头元素但不移除。
    • is_empty():检查队列是否为空。
  • 栈的核心操作push(item)(压栈)、pop()(弹栈)、peek()(查看栈顶)。
  • 为什么用两个栈:输入栈直接接收入队元素,但出队时需要反转顺序。输出栈用于存储反转后的元素,这样出队时就能从输出栈弹出最早进入的元素。

核心思路:

  • 入队(enqueue):总是将元素压入输入栈(stack1)。
  • 出队(dequeue)
    • 如果输出栈(stack2)不为空,直接从输出栈弹出元素(这是最早进入的元素)。
    • 如果输出栈为空,则将输入栈的所有元素弹出并压入输出栈(这会反转顺序),然后从输出栈弹出元素。
  • 查看队头(peek):类似出队,但不移除元素。
  • 检查空队列(is_empty):当两个栈都为空时,队列为空。

这种方法的时间复杂度是摊还 O(1)

  • enqueue:总是 O(1)。
  • dequeuepeek:在输出栈为空时需要 O(n) 时间(n 是输入栈的大小),但每个元素最多被移动两次(从输入栈到输出栈),所以平均(摊还)时间复杂度是 O(1)。

步骤详解

  1. 初始化:创建两个空栈,stack1(输入栈)和 stack2(输出栈)。
  2. 入队(enqueue)
    • 直接将新元素压入 stack1。
  3. 出队(dequeue)
    • 如果 stack2 不为空,弹出 stack2 的栈顶元素。
    • 如果 stack2 为空,则将 stack1 的所有元素依次弹出并压入 stack2(这会反转顺序),然后弹出 stack2 的栈顶元素。
  4. 查看队头(peek)
    • 如果 stack2 不为空,返回 stack2 的栈顶元素(不移除)。
    • 如果 stack2 为空,则将 stack1 的所有元素移到 stack2,然后返回 stack2 的栈顶元素。
  5. 检查空队列(is_empty):如果 stack1 和 stack2 都为空,则队列为空。

示例演示

以元素 A、B、C 入队和出队为例:

  1. 入队 A:stack1 = [A],stack2 = []。
  2. 入队 B:stack1 = [A, B],stack2 = []。
  3. 入队 C:stack1 = [A, B, C],stack2 = []。
  4. 出队(dequeue):
    • stack2 为空,将 stack1 的所有元素移到 stack2:stack1 弹出 C → push 到 stack2;弹出 B → push 到 stack2;弹出 A → push 到 stack2。现在 stack1 = [],stack2 = [C, B, A](栈顶是 A)。
    • 弹出 stack2,返回 A(正确,A 是最早进入的)。
  5. 入队 D:stack1 = [D],stack2 = [C, B](栈顶是 B)。
  6. 出队:stack2 不为空,弹出 stack2,返回 B(正确,B 是下一个)。
  7. 查看队头(peek):stack2 栈顶是 C,返回 C(不移除)。

代码示例(Java)

import java.util.Stack;

public class QueueWithTwoStacks {
    private Stack<Integer> inputStack;
    private Stack<Integer> outputStack;

    public QueueWithTwoStacks() {
        inputStack = new Stack<>();
        outputStack = new Stack<>();
    }

    // 入队操作
    public void enqueue(int item) {
        inputStack.push(item);
    }

    // 出队操作
    public int dequeue() {
        if (isEmpty()) {
            throw new IllegalStateException("Queue is empty");
        }
        transferIfNeeded();
        return outputStack.pop();
    }

    // 查看队头元素
    public int peek() {
        if (isEmpty()) {
            throw new IllegalStateException("Queue is empty");
        }
        transferIfNeeded();
        return outputStack.peek();
    }

    // 检查队列是否为空
    public boolean isEmpty() {
        return inputStack.isEmpty() && outputStack.isEmpty();
    }

    // 如果输出栈为空,将输入栈的元素转移到输出栈
    private void transferIfNeeded() {
        if (outputStack.isEmpty()) {
            while (!inputStack.isEmpty()) {
                outputStack.push(inputStack.pop());
            }
        }
    }

    // 获取队列大小
    public int size() {
        return inputStack.size() + outputStack.size();
    }

    public static void main(String[] args) {
        QueueWithTwoStacks queue = new QueueWithTwoStacks();
        
        // 测试入队
        queue.enqueue(1);
        queue.enqueue(2);
        queue.enqueue(3);
        
        // 测试出队和查看队头
        System.out.println("Dequeue: " + queue.dequeue()); // 输出 1
        System.out.println("Peek: " + queue.peek());       // 输出 2
        
        // 继续入队
        queue.enqueue(4);
        
        // 继续出队
        System.out.println("Dequeue: " + queue.dequeue()); // 输出 2
        System.out.println("Dequeue: " + queue.dequeue()); // 输出 3
        System.out.println("Dequeue: " + queue.dequeue()); // 输出 4
        
        // 测试空队列
        System.out.println("Is empty: " + queue.isEmpty()); // 输出 true
    }
}

注意事项

  • 线程安全:此实现在多线程环境下不安全。如果需要线程安全,可以添加锁机制。
  • 语言适配:这个方法在任何支持栈的语言中都适用(如 Java 用 Stack,C++ 用 std::stack)。
  • 为什么有效:通过两个栈的协作,输入栈处理新元素,输出栈维护“队头”元素。反转操作(将输入栈移到输出栈)只在输出栈为空时发生,保证了效率。

这个方法简单且高效,是面试和实际应用中常见的解决方案。如果还有其他问题,比如如何优化或扩展功能,欢迎继续提问!

posted @ 2025-10-02 14:47  NeoLshu  阅读(0)  评论(0)    收藏  举报  来源