栈和队列 - 完整学习指南
📊 知识体系总览

mindmap
root((栈和队列))
栈(Stack)
基本概念
后进先出LIFO
栈顶栈底
压栈出栈
实现方式
数组实现
链表实现
Java Stack类
基本操作
push入栈
pop出栈
peek查看
size大小
empty判空
应用场景
括号匹配
表达式求值
函数调用栈
撤销操作
浏览记录
队列(Queue)
基本概念
先进先出FIFO
队头队尾
入队出队
实现方式
数组队列
链表队列
循环队列
Java Queue接口
基本操作
offer入队
poll出队
peek查看
size大小
isEmpty判空
应用场景
任务调度
消息队列
广度优先搜索
缓存系统
双端队列(Deque)
基本概念
两端操作
栈和队列组合
实现方式
ArrayDeque
LinkedList
应用场景
滑动窗口
回文检查
双向遍历
经典问题
用栈实现队列
用队列实现栈
最小栈
循环队列
栈的合法序列
性能分析
时间复杂度
栈操作: O(1)
队列操作: O(1)
空间复杂度
数组实现: O(n)
链表实现: O(n)
1. 栈(Stack)
1.1 栈的基本概念

graph TD
A[栈 Stack] --> B[核心特性]
A --> C[基本结构]
A --> D[操作方式]
A --> E[现实例子]
B --> B1[后进先出 LIFO]
B --> B2[只能在栈顶操作]
B --> B3[访问受限]
C --> C1[栈顶 Top]
C --> C2[栈底 Bottom]
C --> C3[存储空间]
D --> D1[压栈 Push]
D --> D2[出栈 Pop]
D --> D3[查看 Peek]
E --> E1[盘子叠放]
E --> E2[浏览器后退]
E --> E3[函数调用]
E --> E4[撤销操作]
概念解释:
栈是一种特殊的线性表,只允许在固定的一端(栈顶)进行插入和删除操作。遵循后进先出(LIFO, Last In First Out)原则。
核心特性:
-
操作受限:只能在栈顶进行插入和删除
-
后进先出:最后入栈的元素最先出栈
-
动态大小:可以根据需要动态增长
-
访问受限:不能随机访问中间元素
代码示例 - 基本使用:
import java.util.*;
public class StackBasicDemo {
public static void main(String[] args) {
System.out.println("=== 栈的基本使用 ===");
// 创建栈
Stack<Integer> stack = new Stack<>();
// 1. 压栈操作
System.out.println("1. 压栈操作:");
stack.push(10);
stack.push(20);
stack.push(30);
stack.push(40);
System.out.println("压栈 10, 20, 30, 40 后: " + stack);
// 2. 查看栈顶元素
System.out.println("\n2. 查看栈顶:");
int top = stack.peek();
System.out.println("栈顶元素: " + top);
System.out.println("查看后栈不变: " + stack);
// 3. 出栈操作
System.out.println("\n3. 出栈操作:");
int popped1 = stack.pop();
System.out.println("出栈元素: " + popped1);
System.out.println("出栈后: " + stack);
int popped2 = stack.pop();
System.out.println("再次出栈: " + popped2);
System.out.println("当前栈: " + stack);
// 4. 栈的大小和判空
System.out.println("\n4. 栈信息:");
System.out.println("栈大小: " + stack.size());
System.out.println("是否为空: " + stack.empty());
// 5. 搜索元素
System.out.println("\n5. 搜索元素:");
int position = stack.search(10); // 返回从栈顶开始的1-based位置
System.out.println("元素10的位置: " + position);
System.out.println("元素100的位置: " + stack.search(100)); // -1表示不存在
// 6. 遍历栈
System.out.println("\n6. 遍历栈:");
System.out.println("方法1 - 使用迭代器:");
for (Integer num : stack) {
System.out.print(num + " ");
}
System.out.println();
System.out.println("方法2 - 使用forEach:");
stack.forEach(num -> System.out.print(num + " "));
System.out.println();
// 注意:不能通过索引访问栈元素
// System.out.println(stack.get(0)); // 编译错误
// 7. 清空栈
System.out.println("\n7. 清空栈:");
while (!stack.empty()) {
System.out.println("出栈: " + stack.pop());
}
System.out.println("清空后大小: " + stack.size());
System.out.println("是否为空: " + stack.empty());
// 8. 注意:Stack是线程安全的
System.out.println("\n8. 线程安全性:");
System.out.println("Stack继承自Vector,是线程安全的");
System.out.println("但在高并发环境下,推荐使用ConcurrentLinkedDeque");
}
}
注意易错点:
-
空栈操作:对空栈调用pop()或peek()会抛出EmptyStackException
-
遍历修改:不能在遍历时修改栈结构
-
线程安全:Stack是线程安全的,但性能较差
-
搜索位置:search()返回的是从栈顶开始的1-based位置
-
接口选择:推荐使用Deque接口代替Stack
1.2 栈的模拟实现

classDiagram
direction BT
class MyArrayStack {
-int[] elementData
-int top
-int capacity
+MyArrayStack()
+MyArrayStack(int initialCapacity)
+push(int element) int
+pop() int
+peek() int
+size() int
+isEmpty() boolean
+isFull() boolean
-ensureCapacity(int minCapacity) void
}
class MyLinkedStack {
-Node top
-int size
+MyLinkedStack()
+push(int element) int
+pop() int
+peek() int
+size() int
+isEmpty() boolean
}
class Node {
-int data
-Node next
+Node(int data)
+Node(int data, Node next)
}
MyLinkedStack o-- Node
数组实现栈:
import java.util.Arrays;
// 数组实现的栈
public class MyArrayStack {
private int[] elementData; // 存储元素的数组
private int top; // 栈顶指针
private int capacity; // 栈容量
// 构造方法
public MyArrayStack() {
this(10); // 默认容量10
}
public MyArrayStack(int initialCapacity) {
if (initialCapacity <= 0) {
throw new IllegalArgumentException("初始容量必须大于0");
}
this.capacity = initialCapacity;
this.elementData = new int[capacity];
this.top = -1; // 栈空时top为-1
}
// 压栈
public int push(int element) {
ensureCapacity(top + 2); // 检查是否需要扩容
elementData[++top] = element; // 先移动指针,再赋值
return element;
}
// 出栈
public int pop() {
if (isEmpty()) {
throw new RuntimeException("栈为空,无法出栈");
}
return elementData[top--]; // 先返回值,再移动指针
}
// 查看栈顶元素
public int peek() {
if (isEmpty()) {
throw new RuntimeException("栈为空,无法查看");
}
return elementData[top];
}
// 获取栈大小
public int size() {
return top + 1;
}
// 判断栈是否为空
public boolean isEmpty() {
return top == -1;
}
// 判断栈是否已满
public boolean isFull() {
return top == capacity - 1;
}
// 扩容
private void ensureCapacity(int minCapacity) {
if (minCapacity > capacity) {
int newCapacity = Math.max(capacity * 2, minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
capacity = newCapacity;
System.out.println("栈扩容: " + capacity / 2 + " -> " + capacity);
}
}
// 清空栈
public void clear() {
top = -1;
// 可选:释放数组引用
// elementData = new int[capacity];
}
// 打印栈
public void display() {
if (isEmpty()) {
System.out.println("栈[]");
return;
}
System.out.print("栈[");
for (int i = 0; i <= top; i++) {
System.out.print(elementData[i]);
if (i < top) {
System.out.print(", ");
}
}
System.out.println("]");
}
// 测试代码
public static void main(String[] args) {
System.out.println("=== 数组实现栈测试 ===");
MyArrayStack stack = new MyArrayStack(3);
// 测试压栈
stack.push(10);
stack.push(20);
stack.push(30);
System.out.print("压栈10,20,30: ");
stack.display();
System.out.println("栈大小: " + stack.size());
// 测试自动扩容
stack.push(40);
System.out.print("压栈40触发扩容: ");
stack.display();
// 测试查看栈顶
System.out.println("栈顶元素: " + stack.peek());
// 测试出栈
System.out.println("出栈: " + stack.pop());
System.out.print("出栈后: ");
stack.display();
// 测试判空
System.out.println("栈是否为空: " + stack.isEmpty());
// 测试连续出栈
while (!stack.isEmpty()) {
System.out.println("出栈: " + stack.pop());
}
System.out.println("清空后是否为空: " + stack.isEmpty());
// 测试异常
try {
stack.pop();
} catch (RuntimeException e) {
System.out.println("空栈出栈异常: " + e.getMessage());
}
}
}
链表实现栈:
// 链表节点
class StackNode {
int data;
StackNode next;
public StackNode(int data) {
this.data = data;
this.next = null;
}
public StackNode(int data, StackNode next) {
this.data = data;
this.next = next;
}
}
// 链表实现的栈
public class MyLinkedStack {
private StackNode top; // 栈顶节点
private int size; // 栈大小
public MyLinkedStack() {
this.top = null;
this.size = 0;
}
// 压栈
public int push(int element) {
StackNode newNode = new StackNode(element);
newNode.next = top; // 新节点指向原栈顶
top = newNode; // 更新栈顶
size++;
return element;
}
// 出栈
public int pop() {
if (isEmpty()) {
throw new RuntimeException("栈为空,无法出栈");
}
int value = top.data; // 保存栈顶值
top = top.next; // 移动栈顶指针
size--;
return value;
}
// 查看栈顶
public int peek() {
if (isEmpty()) {
throw new RuntimeException("栈为空,无法查看");
}
return top.data;
}
// 获取大小
public int size() {
return size;
}
// 判断是否为空
public boolean isEmpty() {
return top == null;
}
// 清空栈
public void clear() {
top = null;
size = 0;
}
// 打印栈
public void display() {
if (isEmpty()) {
System.out.println("栈[]");
return;
}
System.out.print("栈[");
StackNode current = top;
while (current != null) {
System.out.print(current.data);
if (current.next != null) {
System.out.print(" -> ");
}
current = current.next;
}
System.out.println("]");
}
// 测试代码
public static void main(String[] args) {
System.out.println("=== 链表实现栈测试 ===");
MyLinkedStack stack = new MyLinkedStack();
// 测试压栈
stack.push(10);
stack.push(20);
stack.push(30);
System.out.print("压栈10,20,30: ");
stack.display();
System.out.println("栈大小: " + stack.size());
// 测试查看栈顶
System.out.println("栈顶元素: " + stack.peek());
// 测试出栈
System.out.println("出栈: " + stack.pop());
System.out.print("出栈后: ");
stack.display();
// 测试判空
System.out.println("栈是否为空: " + stack.isEmpty());
// 测试连续出栈
while (!stack.isEmpty()) {
System.out.println("出栈: " + stack.pop());
}
System.out.println("清空后是否为空: " + stack.isEmpty());
// 测试大量数据
for (int i = 0; i < 1000; i++) {
stack.push(i);
}
System.out.println("压栈1000个元素后大小: " + stack.size());
// 测试内存自动管理
stack.clear();
System.out.println("清空后大小: " + stack.size());
}
}
注意易错点:
-
指针管理:数组实现要注意top指针的位置
-
边界检查:操作前要检查栈是否为空/满
-
扩容策略:数组栈需要考虑扩容和缩容
-
内存泄漏:链表栈删除节点时要断开引用
-
线程安全:自己实现的栈不是线程安全的
-
异常处理:要对非法操作进行合适的异常处理
1.3 栈的应用场景

graph TD
A[栈的应用场景] --> B[表达式求值]
A --> C[括号匹配]
A --> D[函数调用]
A --> E[浏览记录]
A --> F[撤销操作]
A --> G[递归转循环]
A --> H[迷宫求解]
B --> B1[中缀表达式]
B --> B2[后缀表达式]
B --> B3[前缀表达式]
C --> C1[括号有效性]
C --> C2[HTML/XML标签]
C --> C3[代码缩进]
D --> D1[调用栈]
D --> D2[栈帧]
D --> D3[递归深度]
E --> E1[前进后退]
E --> E2[页面历史]
F --> F1[编辑器撤销]
F --> F2[游戏状态]
G --> G1[深度优先搜索]
G --> G2[树遍历]
应用1:括号匹配
import java.util.*;
public class BracketMatching {
// 检查括号是否匹配
public static boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for (char c : s.toCharArray()) {
if (c == '(' || c == '[' || c == '{') {
// 左括号入栈
stack.push(c);
} else if (c == ')' || c == ']' || c == '}') {
// 右括号检查
if (stack.isEmpty()) {
return false; // 栈空说明右括号多余
}
char top = stack.pop();
if (!isMatchingPair(top, c)) {
return false; // 括号不匹配
}
}
// 忽略其他字符
}
return stack.isEmpty(); // 栈空说明所有括号都匹配
}
// 检查括号是否成对
private static boolean isMatchingPair(char left, char right) {
return (left == '(' && right == ')') ||
(left == '[' && right == ']') ||
(left == '{' && right == '}');
}
// 扩展:支持多种括号
public static boolean isValidExtended(String s) {
Map<Character, Character> map = new HashMap<>();
map.put(')', '(');
map.put(']', '[');
map.put('}', '{');
map.put('>', '<');
Stack<Character> stack = new Stack<>();
for (char c : s.toCharArray()) {
if (map.containsValue(c)) {
// 左括号