栈的原理与实现:后进先出的基础数据结构,括号匹配与表达式求值。

一句话算法

栈只在同一端进出元素,最后放进去的元素最先被取出来。

问题模型

我们需要维护一段线性数据,并支持这些操作:

  • 在栈顶加入一个元素。
  • 删除栈顶元素。
  • 查询当前栈顶元素。
  • 判断栈是否为空。

栈的关键限制是:只能操作栈顶,不能直接访问中间元素。

核心直觉

把栈想成一摞盘子。

新盘子只能放在最上面,拿盘子也只能先拿最上面那一个。所以栈天然适合处理“后出现的东西要先解决”的问题,例如括号匹配、递归调用、后缀表达式求值。

“栈”

栈是一种只允许在一端插入和删除的线性结构。这个端点叫栈顶,另一端叫栈底。

算法步骤

用数组实现栈时,维护一个变量 top_pos

  • top_pos 表示栈顶元素后面的第一个空位置。
  • 当前栈内元素区间是 [0, top_pos)
  • 入栈时先写入 sta[top_pos],再让 top_pos++
  • 出栈时让 top_pos--
  • 栈顶元素是 sta[top_pos - 1]

这种写法把空栈自然表示为 top_pos == 0,不需要特殊处理 -1 下标。

算法证明

关键不变量: 数组区间 [0, top_pos) 存放当前栈内元素,并且 sta[top_pos - 1] 是栈顶。

  1. 初始时 top_pos = 0,区间为空,表示空栈,不变量成立。
  2. 入栈 x 时,把 x 放到 sta[top_pos],再移动到下一个空位。新元素处在所有旧元素之后,所以它成为新的栈顶。
  3. 出栈时只删除 sta[top_pos - 1] 这个最后加入的元素,剩下的区间仍然是 [0, top_pos)

因此每次操作后都保持“最后加入的元素在栈顶”,栈满足后进先出。

复杂度分析

  • 入栈、出栈、查询栈顶、判断空栈:O(1)O(1)
  • 空间复杂度:O(n)O(n),其中 nn 是栈容量。

代码实现

cpp
        
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const int maxn = 100000 + 5; template <typename T = int, int siz = maxn> struct MyStack { T sta[siz + 5]; int top_pos = 0; // 指向栈顶元素后面的一个位置 void clear() { top_pos = 0; } void push(const T& x) { sta[top_pos++] = x; } void pop() { --top_pos; } T& top() { return sta[top_pos - 1]; } const T& top() const { return sta[top_pos - 1]; } bool empty() const { return top_pos == 0; } int size() const { return top_pos; } };

测试用例

输入一组操作:

push 3
push 5
top
pop
top

执行过程:

操作 栈内元素 输出
push 3 3
push 5 3 5
top 3 5 5
pop 3
top 3 3

应用分类详解

栈的本质是维护“最近还没有被解决的对象”。

一、匹配与消除

典型模式: 题目中有成对符号、相邻抵消、最近元素配对。

识别信号: 括号匹配、相邻重复删除、碰撞、撤销最近操作。

核心建模: 遇到新的待匹配对象就入栈,遇到能和栈顶配对的对象就弹栈。

应用场景 经典题目 核心思路
括号匹配 leetcodecn-20luogu-P1739 左括号入栈,右括号检查栈顶
相邻消除 leetcodecn-1047 当前字符和栈顶相同就弹出
括号补全 luogu-P1241 栈中保留尚未匹配的位置

二、表达式求值

典型模式: 题目要求计算中缀、后缀或前缀表达式。

识别信号: 运算符优先级、括号、逆波兰表达式。

核心建模: 数字栈保存待计算值,符号栈保存暂时不能计算的运算符。

应用场景 经典题目 核心思路
后缀表达式 luogu-P1449 遇到运算符就弹出两个数计算
中缀表达式 LeetCode 224、227、772 用符号栈处理优先级和括号

三、递归与搜索状态

典型模式: 过程总是先处理最新展开的分支。

识别信号: DFS、回溯、函数调用、撤销现场。

核心建模: 每个栈元素表示一个尚未完成的状态,弹出后继续展开它。

应用场景 经典题目 核心思路
非递归 DFS 图遍历、树遍历 用栈模拟函数调用栈
验证栈序列 luogu-P4387 模拟入栈和出栈过程

经典例题

  • leetcodecn-20 有效的括号:最直接的匹配模型。
  • luogu-P1449 后缀表达式:练习“遇到运算符就结算”的栈模型。
  • luogu-P4387 验证栈序列:考察能否把抽象的进出栈顺序转成模拟。

参考