一、栈(Stack)是什么?
栈是一种数据结构,核心特点是 后进先出(LIFO,Last In First Out) 。
可以把它想象成一摞盘子:最后放上去的盘子,必须最先拿走。
栈的常见操作:
| 操作 | 含义 |
|---|---|
push() |
入栈,将元素放到栈顶 |
pop() |
出栈,移除并返回栈顶元素 |
peek() |
查看栈顶元素,但不移除 |
isEmpty() |
判断栈是否为空 |
例如,依次将 10、20、30 入栈,连续两次出栈,得到的顺序是 30、20。
栈适用于括号匹配、表达式计算、撤销操作等场景。
二、Java 中如何使用栈?
Java 提供了传统的 Stack 类,但刷算法题时,通常更推荐使用 Deque 接口配合 ArrayDeque 实现栈。
arduino
import java.util.ArrayDeque;
import java.util.Deque;
Deque<Integer> stack = new ArrayDeque<>();
stack.push(10);
stack.push(20);
stack.push(30);
System.out.println(stack.peek()); // 30
System.out.println(stack.pop()); // 30
System.out.println(stack.pop()); // 20
System.out.println(stack.isEmpty()); // false
为什么推荐 Deque + ArrayDeque?
Stack是继承自Vector的传统类,方法带有同步机制。ArrayDeque不需要这些同步操作,在单线程算法题中通常更合适。Deque的接口设计更灵活,既可以实现栈,也可以实现队列。
Stack 并非不能使用,只是通常不作为首选。
三、Deque 是什么?
Deque 全称是 Double Ended Queue,即双端队列。
它是一种 Java 接口,允许从队头和队尾添加、删除、查看元素。
| 操作 | 队头 First | 队尾 Last |
|---|---|---|
| 添加 | addFirst() |
addLast() |
| 删除并返回 | removeFirst() |
removeLast() |
| 查看但不删除 | peekFirst() |
peekLast() |
例如:
arduino
Deque<Integer> deque = new ArrayDeque<>();
deque.addFirst(20); // [20]
deque.addLast(30); // [20, 30]
deque.addFirst(10); // [10, 20, 30]
deque.removeFirst(); // 删除 10
deque.removeLast(); // 删除 30
// 最后剩下 [20]
需要注意,removeFirst() 在队列为空时会抛出异常;pollFirst() 则会返回 null。
四、Deque 和 ArrayDeque 有什么区别?
这是 Java 面向接口编程的一个典型例子。
ini
Deque<Integer> stack = new ArrayDeque<>();
其中:
Deque是接口,定义了可以执行哪些操作。ArrayDeque是实现类,使用可调整容量的数组来实现这些操作。stack是引用变量,指向实际创建的ArrayDeque对象。
这与下面的写法是同样的设计思想:
ini
List<Integer> list = new ArrayList<>();
List 是接口,ArrayList 是实现类。
将变量声明为接口类型,可以减少代码对具体实现类的依赖。
五、栈、队列与双端队列的区别
| 数据结构 | 添加方式 | 删除方式 | 核心特点 |
|---|---|---|---|
| 栈 Stack | 栈顶添加 | 栈顶删除 | 后进先出 |
| 队列 Queue | 队尾添加 | 队头删除 | 先进先出 |
| 双端队列 Deque | 两端均可添加 | 两端均可删除 | 两端均可操作 |
Deque 可以通过不同的操作组合实现栈或队列:
- 当作栈: 使用
push()、pop()、peek(),这些操作都作用于队头。 - 当作队列: 使用
offerLast()添加到队尾,使用pollFirst()从队头取出。
六、堆(Heap)与栈(Stack)不是同一种数据结构
虽然中文里经常把它们放在一起说,但堆和栈是两种不同的数据结构。
1. 栈 Stack
- 核心规则:后进先出。
- 典型应用:括号匹配、深度优先搜索、方法调用管理。
2. 堆 Heap
通常指一种满足特定顺序性质的树形数据结构,常见的是二叉堆。
- 最大堆:父节点的值不小于子节点。
- 最小堆:父节点的值不大于子节点。
- 典型应用:优先队列、获取最大值或最小值、寻找第 K 大元素。
还要区分数据结构中的堆与 JVM 堆内存:JVM 堆是主要用于存放对象和数组的运行时内存区域,不要求对象按大小排列。
七、LeetCode 20:有效的括号为什么使用栈?
题目要求判断括号是否正确匹配,例如 "{[]}" 是有效的,而 "([)]" 是无效的。
核心原因是:最后出现的左括号,必须最先匹配。
以 "{[]}" 为例:
- 遇到
{,入栈。 - 遇到
[,入栈。 - 遇到
],检查栈顶是否为[,匹配成功后出栈。 - 遇到
},检查栈顶是否为{,匹配成功后出栈。 - 遍历结束后,栈为空,说明所有括号都匹配成功。
这正好利用了栈的后进先出特性。
核心总结
- 栈: 后进先出。
- 堆: 常见为用于优先队列的树形数据结构。
- Deque: Java 的双端队列接口,也可以用作栈。
- ArrayDeque: Deque 的一种常用实现。
- 刷 Java 算法题: 需要栈时,优先使用
Deque<T> stack = new ArrayDeque<>();。