【数据结构变种】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)。dequeue和peek:在输出栈为空时需要 O(n) 时间(n 是输入栈的大小),但每个元素最多被移动两次(从输入栈到输出栈),所以平均(摊还)时间复杂度是 O(1)。
步骤详解
- 初始化:创建两个空栈,stack1(输入栈)和 stack2(输出栈)。
- 入队(enqueue):
- 直接将新元素压入 stack1。
- 出队(dequeue):
- 如果 stack2 不为空,弹出 stack2 的栈顶元素。
- 如果 stack2 为空,则将 stack1 的所有元素依次弹出并压入 stack2(这会反转顺序),然后弹出 stack2 的栈顶元素。
- 查看队头(peek):
- 如果 stack2 不为空,返回 stack2 的栈顶元素(不移除)。
- 如果 stack2 为空,则将 stack1 的所有元素移到 stack2,然后返回 stack2 的栈顶元素。
- 检查空队列(is_empty):如果 stack1 和 stack2 都为空,则队列为空。
示例演示
以元素 A、B、C 入队和出队为例:
- 入队 A:stack1 = [A],stack2 = []。
- 入队 B:stack1 = [A, B],stack2 = []。
- 入队 C:stack1 = [A, B, C],stack2 = []。
- 出队(dequeue):
- stack2 为空,将 stack1 的所有元素移到 stack2:stack1 弹出 C → push 到 stack2;弹出 B → push 到 stack2;弹出 A → push 到 stack2。现在 stack1 = [],stack2 = [C, B, A](栈顶是 A)。
- 弹出 stack2,返回 A(正确,A 是最早进入的)。
- 入队 D:stack1 = [D],stack2 = [C, B](栈顶是 B)。
- 出队:stack2 不为空,弹出 stack2,返回 B(正确,B 是下一个)。
- 查看队头(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)。 - 为什么有效:通过两个栈的协作,输入栈处理新元素,输出栈维护“队头”元素。反转操作(将输入栈移到输出栈)只在输出栈为空时发生,保证了效率。
这个方法简单且高效,是面试和实际应用中常见的解决方案。如果还有其他问题,比如如何优化或扩展功能,欢迎继续提问!
本文来自博客园,作者:NeoLshu,转载请注明原文链接:https://www.cnblogs.com/neolshu/p/19513690

浙公网安备 33010602011771号