通过表达式树、表达式求值与转换、括号匹配、入栈出栈序列和卡特兰数掌握栈的应用,并理解队列在调度和广度优先处理中的作用。
# 一、表达式树与三种表达式
表达式树的叶结点是操作数,内部结点是操作符;子树先于父结点计算。对于表达式 :
1 | flowchart TB |
对同一棵表达式树进行不同遍历,可得到三种写法:
| 表示法 | 遍历方式 | 的写法 |
|---|---|---|
| 前缀表达式或波兰式 | 前序遍历 | |
| 中缀表达式 | 中序遍历 | |
| 后缀表达式或逆波兰式 | 后序遍历 |
中缀表达式的真实计算顺序依赖优先级和括号;前缀、后缀表达式不需要括号便能唯一确定二元运算顺序。
# 二、中缀表达式求值
中缀求值维护两个栈:
- 操作数栈:保存尚未参与运算的数字或已算出的子表达式值;
- 操作符栈:保存运算符和左括号。
当需执行运算时,必须先弹出右操作数 ,再弹出左操作数 ,计算 并把结果压回操作数栈。对于减法、除法,这个顺序绝不能颠倒。
处理从左到右扫描到的记号时:
- 操作数直接压入操作数栈。
- 左括号直接压入操作符栈。
- 右括号到来时,连续计算直到弹出与之匹配的左括号;左括号不参与计算。
- 普通运算符到来时,若栈顶运算符优先级更高,或优先级相同且当前运算符左结合,则先计算栈顶运算符;之后再将当前运算符入栈。
- 扫描结束后,计算操作符栈中剩余运算符。
1 | flowchart TD |
例题 3.3:中缀表达式求值
计算 。
【解】 关键状态如下:
| 输入 | 操作符栈 | 操作数栈 | 动作 |
|---|---|---|---|
| 空 | 操作数入栈 | ||
| 操作符入栈 | |||
| $+,( $ | 左括号入栈 | ||
| 分别入对应栈 | |||
| 优先级更高,先算 | |||
| 操作数入栈 | |||
| 计算 ,弹出左括号 | |||
| 结束 | 空 | 计算 |
结果为 。
# 三、后缀表达式求值与中缀转后缀
# 1. 后缀表达式求值
后缀表达式只需一个操作数栈:
- 读到操作数,压栈;
- 读到二元操作符,先弹出 ,再弹出 ;
- 计算 ,把结果压栈;
- 扫描结束时,栈中应恰有一个元素,即表达式值。
例如后缀表达式 的计算过程为:
| 读入 | 操作数栈 |
|---|---|
执行减法和除法时,先弹出的值是右操作数。例如栈顶依次为 ,读到减号应计算 ,而不是 。
# 2. 中缀转后缀
转换时只需要操作符栈,操作数直接进入输出序列:
- 操作数:直接输出;
- 左括号:压栈;
- 右括号:弹出并输出直到左括号,随后丢弃左括号;
- 普通操作符:弹出优先级高于它的运算符;对左结合运算符,还要弹出优先级相等的运算符;最后把当前运算符压栈;
- 扫描结束:把剩余运算符依次输出。
例如
转换为后缀式:
指数运算若作为右结合运算符,应与加减乘除的左结合规则分开处理;题目没有说明时,通常只讨论 。
# 四、括号匹配
括号匹配是栈的直接应用。扫描表达式:
- 遇到左括号 、、,压栈;
- 遇到右括号时,栈必须非空,且栈顶必须是对应的左括号;随后弹栈;
- 扫描结束时,栈必须为空。
任一时刻若右括号无法匹配,或扫描结束后仍有左括号残留,序列均不合法。只检查扫描过程中是否出错还不够,必须检查最终栈是否为空。
# 五、入栈出栈序列
给定固定入栈序列和候选出栈序列,判断合法性的可靠方法是模拟:
- 依次把尚未入栈的元素压栈;
- 每次压栈后,只要栈顶等于候选出栈序列的当前元素,就立即弹出;
- 最后若所有元素均已按候选序列弹出,则合法。
1 |
|
例题 3.4:判断出栈序列
入栈序列为 ,判断候选出栈序列 是否可能。
【解】 依次压入 后弹出 ;压入 后弹出 ;此时栈顶依次为 ,可按候选次序全部弹出。因此该序列可能。
# 卡特兰数
若 个不同元素以固定次序进栈,允许在任意合法时刻出栈,则不同合法出栈序列的数目为第 个卡特兰数:
其中 是组合数。递推关系为
本质原因是任意时刻出栈次数不能超过入栈次数;它与合法括号序列的 “任意前缀中右括号数不超过左括号数” 是同一个约束。因此 对括号的合法序列数、 个结点的不同二叉树形态数也都是 。
# 六、队列的典型应用
队列适合 “先到先处理” 的场景:
- 任务调度与缓冲:按到达顺序处理请求、打印任务或消息;
- 广度优先搜索:先入队的结点先扩展,因此按距离层次访问;
- 层序遍历:二叉树中结点出队时,把其孩子按次序入队。
队列不保证 “最新任务优先”;若需要按优先级选择任务,应使用优先队列而不是普通队列。
# 七、考场检查清单
- 表达式计算弹出两个操作数时,先弹出的是右操作数。
- 中缀转后缀的括号不输出;右括号只触发退栈。
- 判括号匹配时,结束后仍要检查栈是否为空。
- 入栈出栈序列判断以模拟为准,不能只凭局部大小关系猜测。
- 卡特兰数的前提是固定入栈次序、每个元素恰好进栈和出栈一次、且不允许空栈出栈。
