栈和队列虽然只是限制了操作位置的线性表,但它们在计算机科学中的应用极广。这篇把 408 中最常考的栈和队列应用讲透。
一、括号匹配
括号匹配是栈最经典的应用之一,编译器检查代码语法时就是用它。
public class BracketMatcher {
/**
* 判断括号字符串是否合法
* 测试:isValid("({[]})") → true
* isValid("({[}])") → false
*/
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for (char c : s.toCharArray()) {
if (c == '(' || c == '[' || c == '{') {
stack.push(c);
} else {
if (stack.isEmpty()) return false;
char top = stack.pop();
if (c == ')' && top != '(') return false;
if (c == ']' && top != '[') return false;
if (c == '}' && top != '{') return false;
}
}
return stack.isEmpty();
}
}
原理: 遇到左括号入栈,遇到右括号检查栈顶是否匹配。最终栈为空则合法。
二、表达式求值
1. 中缀、前缀、后缀
中缀表达式(人看的): A + B × C
前缀表达式(波兰式): + A × B C
后缀表达式(逆波兰式):A B C × +
计算机更适合处理后缀表达式,因为不需要括号和优先级判断。
2. 中缀转后缀
public class InfixToPostfix {
/**
* 中缀表达式转后缀表达式
* 输入:"A+B*C-(D+E)"
* 输出:["A", "B", "C", "*", "+", "D", "E", "+", "-"]
*/
public List<String> convert(String infix) {
// 运算符优先级
Map<Character, Integer> prec = new HashMap<>();
prec.put('+', 1);
prec.put('-', 1);
prec.put('*', 2);
prec.put('/', 2);
Stack<Character> ops = new Stack<>();
List<String> output = new ArrayList<>();
for (int i = 0; i < infix.length(); i++) {
char c = infix.charAt(i);
if (Character.isLetterOrDigit(c)) {
// 操作数直接输出
output.add(String.valueOf(c));
} else if (c == '(') {
ops.push(c);
} else if (c == ')') {
// 弹出直到左括号
while (!ops.isEmpty() && ops.peek() != '(') {
output.add(String.valueOf(ops.pop()));
}
ops.pop(); // 丢弃左括号
} else {
// 运算符:弹出优先级不低于当前的操作符
while (!ops.isEmpty() && ops.peek() != '('
&& prec.get(ops.peek()) >= prec.get(c)) {
output.add(String.valueOf(ops.pop()));
}
ops.push(c);
}
}
// 剩余操作符出栈
while (!ops.isEmpty()) {
output.add(String.valueOf(ops.pop()));
}
return output;
}
}
3. 计算后缀表达式
public class PostfixCalculator {
/**
* 计算后缀表达式的值
* 输入:["2", "3", "+", "4", "*"] → (2+3)*4 = 20
*/
public int evaluate(List<String> postfix) {
Stack<Integer> stack = new Stack<>();
for (String token : postfix) {
if (token.matches("\\d+")) {
stack.push(Integer.parseInt(token));
} else {
int b = stack.pop();
int a = stack.pop();
switch (token) {
case "+": stack.push(a + b); break;
case "-": stack.push(a - b); break;
case "*": stack.push(a * b); break;
case "/": stack.push(a / b); break;
}
}
}
return stack.pop();
}
}
整体流程:
中缀表达式 → 中缀转后缀 → 计算后缀表达式 → 结果
"A+B*C-(D+E)" → ["A","B","C","*","+","D","E","+","-"] → 计算
三、循环队列
1. 为什么需要循环队列
普通顺序队列的问题:
enqueue(A): front=0, rear=1 [A, , , , ]
enqueue(B): front=0, rear=2 [A, B, , , ]
dequeue(): front=1, rear=2 [ , B, , , ] ← 下标0浪费了
循环队列通过取模运算,让数组空间可以复用
2. 完整实现
class CircularQueue {
private int[] data;
private int front; // 队头指针
private int rear; // 队尾指针(指向下一个插入位置)
private int size; // 当前元素个数
private int capacity;
public CircularQueue(int k) {
data = new int[k];
capacity = k;
front = 0;
rear = 0;
size = 0;
}
public boolean enQueue(int value) {
if (isFull()) return false;
data[rear] = value;
rear = (rear + 1) % capacity;
size++;
return true;
}
public boolean deQueue() {
if (isEmpty()) return false;
front = (front + 1) % capacity;
size--;
return true;
}
public int Front() {
if (isEmpty()) return -1;
return data[front];
}
public int Rear() {
if (isEmpty()) return -1;
// 返回 rear 前一个位置的元素
int idx = (rear - 1 + capacity) % capacity;
return data[idx];
}
public boolean isEmpty() {
return size == 0;
}
public boolean isFull() {
return size == capacity;
}
}
3. 判空判满的两种方式
方式一:size 字段(推荐)
isEmpty() → size == 0
isFull() → size == capacity
简单直观
方式二:牺牲一个存储单元
isEmpty() → front == rear
isFull() → (rear + 1) % capacity == front
不占额外空间,但浪费一个数组元素
四、栈与队列的相互实现
1. 两个栈实现队列
class QueueByTwoStacks {
private Stack<Integer> inStack; // 入队栈
private Stack<Integer> outStack; // 出队栈
public QueueByTwoStacks() {
inStack = new Stack<>();
outStack = new Stack<>();
}
public void push(int x) {
inStack.push(x);
}
public int pop() {
if (outStack.isEmpty()) {
while (!inStack.isEmpty()) {
outStack.push(inStack.pop());
}
}
return outStack.pop();
}
}
2. 两个队列实现栈
class StackByTwoQueues {
private Queue<Integer> queue1;
private Queue<Integer> queue2;
public StackByTwoQueues() {
queue1 = new LinkedList<>();
queue2 = new LinkedList<>();
}
public void push(int x) {
queue1.offer(x);
}
public int pop() {
// 除了队尾元素,全部移到 queue2
while (queue1.size() > 1) {
queue2.offer(queue1.poll());
}
int result = queue1.poll();
// 交换两个队列的引用
Queue<Integer> temp = queue1;
queue1 = queue2;
queue2 = temp;
return result;
}
}
五、单调栈
单调栈是栈的高级应用,用于解决"下一个更大元素"类问题。
/**
* 下一个更大元素
* 输入:[2, 1, 2, 4, 3]
* 输出:[4, 2, 4, -1, -1]
*
* 维护一个单调递减栈
*/
public int[] nextGreaterElement(int[] nums) {
int n = nums.length;
int[] result = new int[n];
Arrays.fill(result, -1);
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < n; i++) {
while (!stack.isEmpty() && nums[i] > nums[stack.peek()]) {
int idx = stack.pop();
result[idx] = nums[i];
}
stack.push(i);
}
return result;
}
六、408 考研常见考题
题1:栈在括号匹配中的应用
表达式:{[()()]}([])
判断过程:
{ → 入栈
[ → 入栈
( → 入栈
) → 弹出 ( → 匹配 ✓
( → 入栈
) → 弹出 ( → 匹配 ✓
] → 弹出 [ → 匹配 ✓
} → 弹出 { → 匹配 ✓
( → 入栈
[ → 入栈
] → 弹出 [ → 匹配 ✓
) → 弹出 ( → 匹配 ✓
栈空 → 完全匹配
题2:循环队列的存储
数组 A[0..m-1] 存放循环队列,front=5, rear=2
队中元素个数 = (rear - front + m) % m = (2-5+8)%8 = 5
题3:表达式求值
中缀:A + B * (C - D) - E / F
后缀:A B C D - * + E F / -
计算后缀时,用栈存操作数
遇到操作数入栈,遇到操作符弹出两个计算后入栈
七、总结
栈的应用:
括号匹配 → 左入右出,最后判空
表达式求值 → 中缀转后缀,再计算
函数调用 → JVM 虚拟机栈
队列的应用:
循环队列 → 取模运算,空间复用
任务调度 → 线程池任务队列
树的遍历 → 层序遍历(BFS)
💡 觉得有用的话,点赞 + 关注【张老师技术栈】吧!每周更新 Java/Python/爬虫 实战干货,不让你白来。

7073

被折叠的 条评论
为什么被折叠?



