后缀表达式
中缀表达式是将运算符放在操作数中间,而后缀表达式则是将运算符放在操作数后面,同时也不需要括号,直接上个例子:
| 中缀表达式 | 后缀表达式 |
|---|---|
(a+b)*c-(d+e)/f |
ab+c*de+f/- |
你会发现没有括号的后缀表达式非常适合计算机来处理:
- 准备一个栈
- 从左至右遍历表达式
- 遍历时遇到操作数则入栈
- 遍历时运算符则出栈一定数量的操作数,将运算结果入栈
- 当表达式遍历完成,栈中应当只有一个数,即为表达式结果
既然后缀表达式这么好用,那么我们的问题就在于如何将中缀表达式简简洁高效地转换成后缀表达式,如果和模拟法一样复杂,那么后缀表达式就没有意义。
基于栈
中缀表达式的复杂之处在于其括号,要匹配括号最高效地办法就是利用栈,在这之上改进就可以转换成后缀表达式。
从左至右遍历表达式:
- 遇到操作数直接输出
- 遇到运算符则分为以下情况:
- 栈为空:该运算符直接入栈
- 栈顶为左括号:该运算符直接入栈
- 栈顶运算符优先级较低:该运算符直接入栈
- 栈顶运算符优先级较高或相等:出栈并输出,再次比较该运算符与栈顶运算符
- 遇到左括号则直接入栈
- 遇到右括号则依次出栈并输出,直至遇到左括号,将左括号出栈,与右括号一同舍弃
- 遍历完成,出栈并输出所有运算符
当然上述只限于二元运算符,如果有一元运算符则需要做出一定改动,但是并不影响整体结构。
同时,以上输出运算符时其实可以直接进行计算,没必要等表达式处理完毕再从头遍历开始计算,输出时直接压入另一个栈就好。
括号法
上面说到中缀表达式的复杂之处在于其括号,运算符有优先级再加上括号改变优先级才是其复杂的原因,如果我们让优先级只有一套规则,没有特权就会简单许多,于是我们可以在不改变表达式运算优先级的情况下,在所有子表达式外都加上括号,就可以很轻松地转换成后缀表达式。
还是以 (a+b)*c-(d+e)/f 为例:
- 加括号变成:
(((a+b)*c)-((d+e)/f)) - 将运算符移动至当前子表达式的括号右侧:
(((ab)+c)*((de)+f)/)- - 去掉所有括号:
ab+c*de+f/-
括号法复杂的点在加括号上,后面两步加起来只要一个遍历就能完成,但是我没有实现过,故不作评论。
语法树
不得不说树在哪里都能插上一手。我们按照以下规则构建一棵二叉树:
- 叶子节点都是操作数
- 非叶子节点都是运算符
- 根节点的优先级低于子节点
如果构建出该树,则通过先序/中序/后序遍历,我们分别可以得到前缀/中缀/后缀表达式(二叉树