(c4)栈应用:中缀表达式求值

官方信息技术老师·12 页·深入(追求细节与边界)·0 次浏览·3 天前
栈应用中缀求值优先级边界处理

(c4)栈应用:中缀表达式

把双栈协作、优先级判定与括号嵌套讲到底

按 空格/→ 演示下一步

1 / 12 页

全部页面点击任意一页,跳回舞台从这页播放

栈应用中缀求值优先级边界处理

(c4)栈应用:中缀表达式

把双栈协作、优先级判定与括号嵌套讲到底

1第 1 页 · (c4)栈应用:中缀表达式

把玩

你随手写 3+5×4,脑子秒出 23。但计算机没有常识,它需要明确的规则——两个栈配合,一次扫描,按优先级结算,这就是今天要拆开的齿轮。

双栈分工
操作数栈装数字,运算符栈装符号,各管各的
优先级驱动
新运算符来了,若优先级不高于栈顶,就先结算栈顶
括号挂起机制
左括号压栈暂停比较,右括号触发清算到匹配左括号
单遍线性扫描
从左到右只走一遍,每个 token 只进栈出栈一次
收银台逐件结算对应 →双栈求值过程

商品(操作数)逐个上扫码台,遇到折扣(运算符)就回头把之前没结的清掉

2第 2 页 · 把玩

构思

上一页我们玩过 3+5×2、((1+2))×3 等表达式,栈都能算对。但为什么「先乘除后加减」能被一个先进后出的栈管住?这页讲构思。

双栈职责
操作数栈存数字,运算符栈存符号,角色分明不混用
优先级驱动
新符号优先级不高于栈顶时,先弹旧符号计算再入栈
括号作边界
左括号压栈并把内部优先级抬高,右括号触发连续弹栈
适用与局限
标准算术表达式;函数与自定义运算符需扩展优先级表
医院分诊台对应 →双栈求值

重患(高优先级运算符)先抢救,轻患(低优先级)继续排队等候

3第 3 页 · 构思

实例

上一页我们搭好了双栈求值的思路。现在用 '3 + 4 × 2 - 1' 走一遍,看运算符栈和操作数栈如何配合处理优先级与结合性。

表达式样例
3 + 4 × 2 - 1,结果应为 10(先 4×2=8,再 3+8=11,最后 11-1=10)
逐 token 扫描
从左到右读入,每个数字或运算符是一个 token,触发对应动作
操作数栈变化
遇数字压栈;遇运算符弹出栈顶两数计算,结果再压回
运算符栈优先级
当前运算符 ≤ 栈顶优先级时,先弹栈顶算完,再压入当前
火车调车场对应 →双栈求值

数字像车厢依次通过;运算符像调度命令压在站台,优先级决定何时执行

3+4×21=3+81=103 + 4 \times 2 - 1 = 3 + 8 - 1 = 10
4第 4 页 · 实例

算法框架

上一页我们手工走了一遍 3+4×2-5 的求值。但每次都靠人脑比对优先级太麻烦——能否让机器按一套规则自动完成?这就需要把思路固化成算法框架。

双栈结构
一个栈存数字(操作数栈),一个栈存运算符号(运算符栈)
优先级比较
当前符号优先级 ≤ 栈顶符号时,先把栈顶那一步计算掉
括号处理
左括号直接入栈;遇到右括号则一直弹栈计算,直到弹出左括号
扫尾清栈
表达式扫描完后,把运算符栈剩余符号依次弹栈计算完
工厂两条流水线对应 →双栈求值

一条摆零件(数字),一条排加工指令(符号);新指令优先级低时先把栈顶工序做完

curtop先弹出 top 与栈顶两个数字做运算\text{cur} \le \text{top} \Rightarrow \text{先弹出 top 与栈顶两个数字做运算}
5第 5 页 · 算法框架

算法细节

算法框架搭好了,但真正决定它跑得对不对的,是几个细节:遇到运算符时该不该入栈、什么时候弹栈算,以及怎么把「3」「14」这种连续字符识别成一个数。

优先级比较规则
扫描到运算符时与栈顶比较:当前更高则入栈,否则弹栈计算
结合性分流
优先级相等时:左结合先弹栈再入栈,右结合则相反(幂运算典型)
左括号的延迟生效
左括号直接入栈,但优先级视为 0,直到遇见右括号才弹至左括号
多位数累积读取
遇到数字字符连续读取直到非数字,整体压入数字栈(而非逐位)
扫描结束的弹栈
表达式读完后,运算符栈剩下的依次弹栈计算,数字栈顶即为结果
排队上公交的让位规则对应 →运算符入栈与弹栈的判断

VIP(高优先级)直接上车;同级别老人(先到)先坐下,新人挤(结合性决定谁让谁)

6第 6 页 · 算法细节

04C4−6A 实例A

04C4−6A 实例A:定义、要点与典型应用

04C4−6A 实例A
04C4−6A 实例A:定义、要点与典型应用
7第 7 页 · 04C4−6A 实例A

04C4−6B 实例B

实例A 演示了最简单的「一数一符」求值。真实表达式常常不止一个运算符——实例B 用 3+4×2−1 演示多个运算符同时出现时,栈如何决定「谁先算」。

实例表达式
3+4×2−1:四个数字、三个运算符,涉及两种优先级
数字栈:照单全收
扫到数字就直接入栈,不参与任何比较
符号栈:随时比高
新符号进来时与栈顶比优先级,高就触发计算
出栈判定
栈顶优先级 ≥ 新符号时,弹一符两数算一次,结果回数字栈
摞起来的便签对应 →符号栈的优先级判定

新便签放上去前先看顶上那张——顶上那张更急就先处理掉,否则压在最上面

3+4×21=103+4\times 2-1=10
8第 8 页 · 04C4−6B 实例B

04C4−6C 实例C

实例B只用了一道带一层括号的式子热身。真实的算式往往嵌套更深、运算符更杂。实例C挑了一道括号多层嵌套、+、−、×、÷ 齐上阵的考题,看算法在反复压栈出栈的节拍里是否还稳得住。

复合表达式
括号多层嵌套,+、−、×、÷ 四种运算符混合出现
括号即占位符
'('、')' 入栈只为界定子表达式,遇 ')' 才弹出到 '(' 并结算
子结果回灌
子表达式一旦算完,结果立刻压回数字栈,作为外层操作数
优先级裁决
栈顶运算符优先级 ≥ 当前就读符时,立即弹出计算
草稿纸上列式演算对应 →符号栈 + 数字栈

草稿纸中央一行行写没算完的运算(符号栈),右边列每步的中间结果(数字栈)

((2+3)×46)÷2+1=?((2 + 3) \times 4 - 6) \div 2 + 1 = ?
9第 9 页 · 04C4−6C 实例C

实例D

前面三例分别演示了基本运算、优先级、括号。今天这例 '100-50/2*3+4' 把四种运算符、两位数、混合优先级都凑齐,看算法能不能扛住真实场景的考验。

表达式本身
'100-50/2*3+4',含四则运算的多位数混合式
运算符种类
+、-、*、/ 四种运算符全部出现
操作数特点
含两位数 100、50,须处理多字符数字的拼接
优先级规则
*、/ 优先级高于 +、-,先算乘除后算加减
结合性
同优先级从左到右,50/2*3 应得 75 而非 50/6
食堂打饭排队对应 →运算符优先级

*、/ 像 VIP 插队到 +、- 前面,同级按到达先后处理

10050/23+4=100253+4=10075+4=29100-50/2*3+4 = 100-25*3+4 = 100-75+4 = 29
10第 10 页 · 实例D

本节要点

  • 双栈分工:操作数栈求值,运算符栈定序
  • 优先级决定弹栈时机,括号强制内部先算
  • 同级左结合:先弹再压同优先级算符
  • 扫描结束清空残栈,即为算法终止信号
延伸主题:前缀/后缀表达式求值Shunting-yard 完整算法含函数调用与自定义运算
11第 11 页 · 本节要点

课后思考

先独立想 1 分钟,再看参考答案——问题比答案更有价值。

1为什么中缀表达式求值需要两个栈,而不是一个?运算符栈和操作数栈各自的职责是什么?

参考答案本质是「何时算」与「算什么」两条线的分离。运算符栈通过优先级和括号决定运算顺序;操作数栈存放待运算的数字。两个职责解耦,才能在遇到新运算符时正确判断该立刻计算还是先压栈等待。

2如果输入表达式里含有空格或多位数(比如「 12 + 34 」),算法需要做哪些调整才能正确处理?

参考答案属于字符串解析层的改动:扫描时跳过空格,遇到数字字符就连续拼接直到非数字,形成完整整数。栈的进出逻辑不受影响,核心算法框架照常运行。

3如果改变运算符优先级规则(例如规定「加法优先于乘法」),双栈算法框架还适用吗?需要改哪里?

参考答案完全适用。优先级只是「两个运算符谁先算」的一张比较表,框架(遇运算符与栈顶比较、决定弹栈或压栈)无需改动,只需替换优先级表——这正是该算法的优雅之处。

12第 12 页 · 课后思考