一、栈:后进先出的线性数据结构
1.1 栈的基本概念与特性
栈(Stack)是一种后进先出(Last-In-First-Out, LIFO)的线性数据结构,只允许在固定的一端(栈顶)进行插入和删除操作。这种特性使得栈在各种计算机科学场景中有着广泛的应用。

栈的核心操作:
- push(压栈):将元素添加到栈顶
- pop(弹栈):移除并返回栈顶元素
- peek(查看):返回栈顶元素但不移除
- isEmpty:检查栈是否为空
- size:返回栈中元素数量
1.2 栈的ADT(抽象数据类型)定义
public interface Stack<T> {
void push(T element); // 压栈操作
T pop(); // 弹栈操作
T peek(); // 查看栈顶元素
boolean isEmpty(); // 判断栈是否为空
int size(); // 获取栈大小
}
二、栈的实现方式
2.1 基于数组的实现
public class ArrayStack<T> implements Stack<T> {
private static final int DEFAULT_CAPACITY = 10;
private T[] elements;
private int top; // 栈顶指针
public ArrayStack() {
this(DEFAULT_CAPACITY);
}
@SuppressWarnings("unchecked")
public ArrayStack(int capacity) {
elements = (T[]) new Object[capacity];
top = -1;
}
@Override
public void push(T element) {
if (top == elements.length - 1) {
resize(2 * elements.length); // 动态扩容
}
elements[++top] = element;
}
@Override
public T pop() {
if (isEmpty()) {
throw new EmptyStackException();
}
T element = elements[top];
elements[top--] = null; // 避免内存泄漏
// 缩容机制
if (top > 0 && top == elements.length / 4) {
resize(elements.length / 2);
}
return element;
}
@Override
public T peek() {
if (isEmpty()) {
throw new EmptyStackException();
}
return elements[top];
}
@Override
public boolean isEmpty() {
return top == -1;
}
@Override
public int size() {
return top + 1;
}
private void resize(int newCapacity) {
@SuppressWarnings("unchecked")
T[] newElements = (T[]) new Object[newCapacity];
for (int i = 0; i <= top; i++) {
newElements[i] = elements[i];
}
elements = newElements;
}
}
2.2 基于链表的实现
public class LinkedStack<T> implements Stack<T> {
private static class Node<T> {
T data;
Node<T> next;
Node(T data) {
this.data = data;
}
}
private Node<T> top;
private int size;
public LinkedStack() {
top = null;
size = 0;
}
@Override
public void push(T element) {
Node<T> newNode = new Node<>(element);
newNode.next = top;
top = newNode;
size++;
}
@Override
public T pop() {
if (isEmpty()) {
throw new EmptyStackException();
}
T element = top.data;
top = top.next;
size--;
return element;
}
@Override
public T peek() {
if (isEmpty()) {
throw new EmptyStackException();
}
return top.data;
}
@Override
public boolean isEmpty() {
return top == null;
}
@Override
public int size() {
return size;
}
}
2.3 两种实现方式的对比
|
特性 |
数组实现 |
链表实现 |
|
时间复杂度 |
所有操作O(1) |
所有操作O(1) |
|
空间复杂度 |
需要预先分配空间 |
动态分配,无空间浪费 |
|
内存使用 |
可能有空间浪费 |
每个元素需要额外指针空间 |
|
扩容成本 |
需要复制整个数组 |
无需扩容 |
|
适用场景 |
元素数量可预测 |
元素数量变化大 |
三、栈的核心算法与应用
3.1 括号匹配问题
问题描述:检查字符串中的括号是否正确匹配。
public boolean isValidParentheses(String s) {
Stack<Character> stack = new ArrayStack<>();
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 ((c == ')' && top != '(') ||
(c == ']' && top != '[') ||
(c == '}' && top != '{')) {
return false;
}
}
}
return stack.isEmpty();
}
// 测试示例
System.out.println(isValidParentheses("()[]{}")); // true
System.out.println(isValidParentheses("([)]")); // false
3.2 表达式求值
中缀表达式转后缀表达式(逆波兰表达式):
public String infixToPostfix(String infix) {
StringBuilder postfix = new StringBuilder();
Stack<Character> stack = new ArrayStack<>();
for (char c : infix.toCharArray()) {
if (Character.isDigit(c)) {
postfix.append(c).append(' ');
} else if (c == '(') {
stack.push(c);
} else if (c == ')') {
while (!stack.isEmpty() && stack.peek() != '(') {
postfix.append(stack.pop()).append(' ');
}
stack.pop(); // 弹出 '('
} else if (isOperator(c)) {
while (!stack.isEmpty() && precedence(stack.peek()) >= precedence(c)) {
postfix.append(stack.pop()).append(' ');
}
stack.push(c);
}
}
while (!stack.isEmpty()) {
postfix.append(stack.pop()).append(' ');
}
return postfix.toString().trim();
}
private boolean isOperator(char c) {
return c == '+' || c == '-' || c == '*' || c == '/';
}
private int precedence(char operator) {
switch (operator) {
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
default:
return 0;
}
}
后缀表达式求值:
public int evaluatePostfix(String postfix) {
Stack<Integer> stack = new ArrayStack<>();
String[] tokens = postfix.split("\\s+");
for (String token : tokens) {
if (token.matches("\\d+")) {
stack.push(Integer.parseInt(token));
} else {
int operand2 = stack.pop();
int operand1 = stack.pop();
int result = applyOperation(token.charAt(0), operand1, operand2);
stack.push(result);
}
}
return stack.pop();
}
private int applyOperation(char operator, int a, int b) {
switch (operator) {
case '+': return a + b;
case '-': return a - b;
case '*': return a * b;
case '/':
if (b == 0) throw new ArithmeticException("Division by zero");
return a / b;
default: throw new IllegalArgumentException("Invalid operator: " + operator);
}
}
3.3 栈在递归中的应用
递归的栈模拟 - 以斐波那契数列为例:
// 递归实现
public int fibonacciRecursive(int n) {
if (n <= 1) return n;
return fibonacciRecursive(n - 1) + fibonacciRecursive(n - 2);
}
// 栈模拟递归实现
public int fibonacciIterative(int n) {
if (n <= 1) return n;
Stack<Integer> stack = new ArrayStack<>();
stack.push(n);
int result = 0;
while (!stack.isEmpty()) {
int current = stack.pop();
if (current <= 1) {
result += current;
} else {
stack.push(current - 1);
stack.push(current - 2);
}
}
return result;
}
3.4 单调栈算法
下一个更大元素问题:
public int[] nextGreaterElement(int[] nums) {
int[] result = new int[nums.length];
Arrays.fill(result, -1);
Stack<Integer> stack = new ArrayStack<>();
for (int i = 0; i < nums.length; i++) {
while (!stack.isEmpty() && nums[i] > nums[stack.peek()]) {
int index = stack.pop();
result[index] = nums[i];
}
stack.push(i);
}
return result;
}
// 测试示例
int[] nums = {2, 1, 2, 4, 3};
int[] result = nextGreaterElement(nums);
// 结果: [4, 2, 4, -1, -1]
四、栈的实际应用场景
4.1 浏览器历史记录
public class BrowserHistory {
private Stack<String> backStack;
private Stack<String> forwardStack;
private String currentPage;
public BrowserHistory(String homepage) {
backStack = new ArrayStack<>();
forwardStack = new ArrayStack<>();
currentPage = homepage;
}
public void visit(String url) {
backStack.push(currentPage);
currentPage = url;
forwardStack = new ArrayStack<>(); // 清空前向历史
System.out.println("Visiting: " + url);
}
public String back(int steps) {
while (steps > 0 && !backStack.isEmpty()) {
forwardStack.push(currentPage);
currentPage = backStack.pop();
steps--;
}
System.out.println("Back to: " + currentPage);
return currentPage;
}
public String forward(int steps) {
while (steps > 0 && !forwardStack.isEmpty()) {
backStack.push(currentPage);
currentPage = forwardStack.pop();
steps--;
}
System.out.println("Forward to: " + currentPage);
return currentPage;
}
public String getCurrentPage() {
return currentPage;
}
}
// 使用示例
BrowserHistory browser = new BrowserHistory("homepage.com");
browser.visit("google.com");
browser.visit("github.com");
browser.back(1); // 返回 google.com
browser.forward(1); // 前进到 github.com
4.2 文本编辑器中的撤销/重做功能
public class TextEditor {
private Stack<String> undoStack;
private Stack<String> redoStack;
private StringBuilder content;
public TextEditor() {
undoStack = new ArrayStack<>();
redoStack = new ArrayStack<>();
content = new StringBuilder();
}
public void type(String text) {
undoStack.push(content.toString());
content.append(text);
redoStack.clear(); // 新的输入清除重做历史
System.out.println("Typed: " + text);
}
public void undo() {
if (!undoStack.isEmpty()) {
redoStack.push(content.toString());
content = new StringBuilder(undoStack.pop());
System.out.println("Undo to: " + content.toString());
}
}
public void redo() {
if (!redoStack.isEmpty()) {
undoStack.push(content.toString());
content = new StringBuilder(redoStack.pop());
System.out.println("Redo to: " + content.toString());
}
}
public String getContent() {
return content.toString();
}
}
4.3 函数调用栈
public class FunctionCallStack {
private static void functionA() {
System.out.println("进入函数A");
functionB();
System.out.println("离开函数A");
}
private static void functionB() {
System.out.println("进入函数B");
functionC();
System.out.println("离开函数B");
}
private static void functionC() {
System.out.println("进入函数C");
System.out.println("执行函数C的操作");
System.out.println("离开函数C");
}
public static void main(String[] args) {
System.out.println("程序开始执行");
functionA();
System.out.println("程序执行结束");
}
}
输出结果:
程序开始执行 进入函数A 进入函数B 进入函数C 执行函数C的操作 离开函数C 离开函数B 离开函数A 程序执行结束
4.4 深度优先搜索(DFS)
public class GraphDFS {
private Map<Integer, List<Integer>> graph;
public GraphDFS() {
graph = new HashMap<>();
}
public void addEdge(int u, int v) {
graph.putIfAbsent(u, new ArrayList<>());
graph.get(u).add(v);
}
public void dfs(int start) {
Set<Integer> visited = new HashSet<>();
Stack<Integer> stack = new ArrayStack<>();
stack.push(start);
while (!stack.isEmpty()) {
int node = stack.pop();
if (!visited.contains(node)) {
System.out.print(node + " ");
visited.add(node);
if (graph.containsKey(node)) {
// 逆序压栈以保证正常的遍历顺序
List<Integer> neighbors = graph.get(node);
for (int i = neighbors.size() - 1; i >= 0; i--) {
int neighbor = neighbors.get(i);
if (!visited.contains(neighbor)) {
stack.push(neighbor);
}
}
}
}
}
}
}
// 使用示例
GraphDFS graph = new GraphDFS();
graph.addEdge(0, 1);
graph.addEdge(0, 2);
graph.addEdge(1, 3);
graph.addEdge(2, 4);
graph.dfs(0); // 输出: 0 2 4 1 3
五、栈的进阶应用与最佳实践
5.1 最小栈实现
支持O(1)时间复杂度获取最小元素的栈:
public class MinStack {
private Stack<Integer> mainStack;
private Stack<Integer> minStack;
public MinStack() {
mainStack = new ArrayStack<>();
minStack = new ArrayStack<>();
}
public void push(int x) {
mainStack.push(x);
if (minStack.isEmpty() || x <= minStack.peek()) {
minStack.push(x);
}
}
public int pop() {
int value = mainStack.pop();
if (value == minStack.peek()) {
minStack.pop();
}
return value;
}
public int top() {
return mainStack.peek();
}
public int getMin() {
return minStack.peek();
}
}
5.2 栈的线程安全实现
public class ThreadSafeStack<T> implements Stack<T> {
private final Stack<T> stack;
private final ReentrantLock lock;
public ThreadSafeStack() {
stack = new LinkedStack<>();
lock = new ReentrantLock();
}
@Override
public void push(T element) {
lock.lock();
try {
stack.push(element);
} finally {
lock.unlock();
}
}
@Override
public T pop() {
lock.lock();
try {
return stack.pop();
} finally {
lock.unlock();
}
}
// 其他方法的实现类似...
}
六、总结与最佳实践
6.1 栈的适用场景总结
- 括号匹配和语法检查:编译器、表达式求值
- 函数调用和递归:程序执行栈、DFS遍历
- 撤销/重做功能:文本编辑器、图形应用
- 浏览历史:浏览器、移动应用
- 算法应用:单调栈、表达式求值
6.2 最佳实践建议
- 选择正确的实现方式:
- 使用数组实现当元素数量可预测时
- 使用链表实现当元素数量变化较大时
- 使用Java内置的Stack类或Deque接口
- 注意栈溢出:
- 递归深度过大时考虑迭代实现
- 使用显式栈替代深层递归
- 线程安全考虑:
- 多线程环境下使用同步栈实现
- 考虑使用ConcurrentLinkedDeque等并发集合
- 内存管理:
- 及时释放不再使用的栈引用
- 监控栈的大小,避免内存泄漏
// Java内置栈的使用
import java.util.Stack;
public class JavaBuiltInStackExample {
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
// 压栈操作
stack.push(1);
stack.push(2);
stack.push(3);
// 查看栈顶元素
System.out.println("Top element: " + stack.peek()); // 3
// 弹栈操作
while (!stack.isEmpty()) {
System.out.println("Popped: " + stack.pop());
}
// 输出: 3, 2, 1
}
}
栈作为一种基础而强大的数据结构,在计算机科学的各个领域都有着广泛的应用。掌握栈的原理和实现,理解其在不同场景下的应用,是每个Java开发者必备的技能。通过合理的栈结构选择和应用,可以编写出更高效、更健壮的程序。
5万+

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



