1. 什么是栈
栈(Stack)是一种**后进先出(LIFO,Last In First Out)**的线性数据结构。它只允许在一端(称为栈顶 top)进行插入和删除操作,另一端(栈底 bottom)是固定的。可以把栈想象成一摞盘子:后放上去的盘子总是最先被拿走。
栈的核心操作只有两个:
- 入栈(push):把元素放到栈顶。
- 出栈(pop):把栈顶元素移除。
此外还有两个常用辅助操作:
- 取栈顶(top / peek):查看栈顶元素但不移除。
- 判空(empty):判断栈中是否还有元素。
2. 栈的作用
栈的核心价值在于保存"现场"并支持回溯。因为后进先出的特性,栈天然适合处理需要"先记住状态、稍后再恢复"的场景。具体来说,栈的作用体现在以下几个方面:
- 保存调用现场:函数调用时,系统用栈保存返回地址、局部变量和参数,函数返回时再从栈中恢复。
- 实现嵌套与递归:递归调用本质上是不断入栈,回溯时不断出栈。
- 表达式求值与语法解析:编译器用栈处理运算符优先级、括号匹配等问题。
- 撤销与回退:编辑器的撤销操作、浏览器的后退功能,本质上都是把历史状态压入栈中。
3. 栈的常见应用场景
3.1 函数调用与递归
程序运行时,每次函数调用都会在系统栈中压入一个栈帧,保存返回地址和局部变量。递归函数尤其依赖栈,递归深度过大时会导致栈溢出(stack overflow)。
3.2 括号匹配
编译器在解析代码时,用栈检查括号是否成对匹配。遇到左括号入栈,遇到右括号出栈并比对类型。
3.3 表达式求值
中缀表达式转后缀表达式、后缀表达式求值,都依赖栈来暂存操作数和运算符。
3.4 浏览器的前进后退
浏览器用两个栈分别记录后退和前进的历史页面。点击后退时,当前页入前进栈,后退栈出栈;点击前进时则相反。
3.5 深度优先搜索(DFS)
图的深度优先遍历可以用栈实现,无论是显式使用栈还是通过递归隐式使用栈。
4. 栈与队列的区别
栈和队列都是线性数据结构,但它们的操作规则完全不同:
| 对比项 | 栈(Stack) | 队列(Queue) |
|---|---|---|
| 操作规则 | 后进先出(LIFO) | 先进先出(FIFO) |
| 插入位置 | 栈顶 | 队尾 |
| 删除位置 | 栈顶 | 队头 |
| 典型比喻 | 一摞盘子 | 排队买票 |
| 主要应用 | 函数调用、括号匹配、表达式求值 | 任务调度、消息队列、广度优先搜索 |
5. C/C++ 代码实现
5.1 使用标准库(C++)
C++ 标准库提供了 std::stack,可以直接使用:
cpp
#include <iostream>
#include <stack>
using namespace std;
int main() {
stack<int> s;
// 入栈
s.push(10);
s.push(20);
s.push(30);
cout << "栈顶元素: " << s.top() << endl; // 30
// 出栈
s.pop();
cout << "出栈后栈顶: " << s.top() << endl; // 20
// 判空
cout << "栈是否为空: " << (s.empty() ? "是" : "否") << endl;
// 遍历(注意:栈只能从栈顶访问)
while (!s.empty()) {
cout << s.top() << " ";
s.pop();
}
cout << endl;
return 0;
}
5.2 数组实现(C 语言)
用数组模拟栈,需要维护一个栈顶指针:
c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_SIZE 100
typedef struct {
int data[MAX_SIZE];
int top; // 栈顶下标,-1 表示空栈
} Stack;
// 初始化
void init(Stack *s) {
s->top = -1;
}
// 判空
bool isEmpty(Stack *s) {
return s->top == -1;
}
// 判满
bool isFull(Stack *s) {
return s->top == MAX_SIZE - 1;
}
// 入栈
bool push(Stack *s, int value) {
if (isFull(s)) {
printf("栈已满,无法入栈\n");
return false;
}
s->data[++s->top] = value;
return true;
}
// 出栈
bool pop(Stack *s, int *value) {
if (isEmpty(s)) {
printf("栈为空,无法出栈\n");
return false;
}
*value = s->data[s->top--];
return true;
}
// 取栈顶
bool peek(Stack *s, int *value) {
if (isEmpty(s)) {
printf("栈为空\n");
return false;
}
*value = s->data[s->top];
return true;
}
int main() {
Stack s;
init(&s);
push(&s, 10);
push(&s, 20);
push(&s, 30);
int val;
peek(&s, &val);
printf("栈顶元素: %d\n", val); // 30
while (pop(&s, &val)) {
printf("%d ", val);
}
printf("\n");
return 0;
}
5.3 链表实现(C 语言)
链表实现栈可以动态扩容,不受固定大小限制:
c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct Node {
int data;
struct Node *next;
} Node;
typedef struct {
Node *top; // 栈顶指针
} Stack;
// 初始化
void init(Stack *s) {
s->top = NULL;
}
// 判空
bool isEmpty(Stack *s) {
return s->top == NULL;
}
// 入栈(头插法)
void push(Stack *s, int value) {
Node *newNode = (Node *)malloc(sizeof(Node));
newNode->data = value;
newNode->next = s->top;
s->top = newNode;
}
// 出栈
bool pop(Stack *s, int *value) {
if (isEmpty(s)) {
printf("栈为空,无法出栈\n");
return false;
}
Node *temp = s->top;
*value = temp->data;
s->top = temp->next;
free(temp);
return true;
}
// 取栈顶
bool peek(Stack *s, int *value) {
if (isEmpty(s)) {
printf("栈为空\n");
return false;
}
*value = s->top->data;
return true;
}
int main() {
Stack s;
init(&s);
push(&s, 10);
push(&s, 20);
push(&s, 30);
int val;
peek(&s, &val);
printf("栈顶元素: %d\n", val); // 30
while (pop(&s, &val)) {
printf("%d ", val);
}
printf("\n");
return 0;
}
6. 实战:用栈实现括号匹配
下面用 C++ 实现一个经典的括号匹配检查器,这是栈最典型的应用之一:
cpp
#include <iostream>
#include <stack>
#include <string>
using namespace std;
bool isMatching(char open, char close) {
return (open == '(' && close == ')') ||
(open == '[' && close == ']') ||
(open == '{' && close == '}');
}
bool isValid(string s) {
stack<char> st;
for (char c : s) {
if (c == '(' || c == '[' || c == '{') {
st.push(c); // 左括号入栈
} else if (c == ')' || c == ']' || c == '}') {
if (st.empty() || !isMatching(st.top(), c)) {
return false; // 栈空或类型不匹配
}
st.pop(); // 匹配成功,出栈
}
}
return st.empty(); // 栈空说明全部匹配
}
int main() {
string test1 = "{[()]}";
string test2 = "{[(])}";
string test3 = "((()))";
cout << test1 << " : " << (isValid(test1) ? "合法" : "非法") << endl;
cout << test2 << " : " << (isValid(test2) ? "合法" : "非法") << endl;
cout << test3 << " : " << (isValid(test3) ? "合法" : "非法") << endl;
return 0;
}
7. 总结
栈是一种后进先出的线性数据结构,核心操作是入栈和出栈。它的主要作用是保存现场、支持回溯,广泛应用于函数调用、递归、括号匹配、表达式求值、浏览器历史记录和深度优先搜索等场景。与队列相比,栈的特点是后进先出,而队列是先进先出,两者在操作规则和应用场景上有本质区别。掌握栈的原理和实现,是理解程序运行机制和算法设计的重要基础。