中缀表达式与后缀表达式特点
首先,四则运算中只有4个运算符和两个括号,分别为 +
、-
、*
、/
、(
、)
。这4个运算符都是双目运算符,也就是说每个运算符至少有两个运算目。
其次,中缀表达式的特点是两个运算目分别在运算符的两边,而后缀表达式的运算目按出现次序先后
放在运算符的前面。
也就是说,在输出后缀表达式的时候,如果两个运算目尚未输出,则不得输出操作这两个运算目的运算符。
假设中缀表达式为:a+b-c
则后缀表达式应为:ab+c-
从左到右输出后缀表达式时,如果b
尚未输出,则不得输出+
最后是四则运算符的优先级问题。众所周知, *
和 /
的优先级高于 +
和 -
,当优先级相同时,按照中缀表达式由左向右逐个符号进行计算。
为什么用栈结构?
当我们操作后缀表达式的时候,如何按照正确的顺序输出运算符?
举例:
中缀表达式:
a+b*c-d
我们假设用一个数据结构存储运算符,用一个队列来存储输出顺序,然后按照从左往右的顺序逐个扫描中缀表达式。
- 首先扫描到运算目 a。运算目 a 是整个表达式的第一个,所以可以直接放入输出队列。
输出队列:a
数据结构:
- 然后我们扫描到运算符
+
,但是此时输出队列里没有+
的第二个运算目,所以我们只能先把它放入数据结构里存放起来。
输出队列:a
数据结构:+
- 扫描到运算目 b 。运算目不能影响运算符的顺序,因此直接将它放入输出队列。
输出队列:ab
数据结构:+
- 此时我们还不能将
+
放入输出队列,因为我们还不能确定 b 是不是+
的第二个运算目。(事实上它也确实不是,+
的第二个运算目是b*c
的值) - 扫描到运算符
*
,现在我们可以确定了,实际上 b 是*
的第一个运算目。出于和+
同样考虑,我们把乘号放入数据结构。
输出队列:ab
数据结构:+*
- 扫描到运算目 c ,放入输出队列。
- 虽然四则运算中,乘除已经是优先级的天花板了,但是拓展到更广阔的领域,我们依然不能确定,乘除是最高的优先级,所以和加号一样,暂时不予输出。
输出队列:abc
数据结构:+*
- 扫描到运算目
-
,我们现在可以确定 c 是*
的第二个运算目,所以我们将数据结构里的*
放入输出队列。
输出队列:abc*
数据结构:+
- 此时,剩余在数据结构里的
+
和 扫描中的-
是同级关系,应该先计算a+(b*c的值)
。所以+
优先输出
输出队列:abc*+
数据结构:
- 此时我们仍未扫描到
-
的第二个运算目,所以我们把-
放入数据结构中。
输出队列:abc*+
数据结构:-
- 扫描到运算目 d,放入到输出队列中
输出队列:abc*+d
数据结构:-
- 中缀表达式结束,我们可以确定 d 为运算符
-
的第二个运算目,所以将数据结构中的-
放入到输出队列中。此时后缀表达式全部输出序列完成。
输出队列:abc*+d-
数据结构:
观察可以发现:+
先于 *
进入数据结构,但却是 *
先被数据结构弹出,由此可见,按照该算法,该数据结构具有后进先出的特点,故采用栈结构。
优先级表格设计
计算机没有我们人这么聪明,所以我们需要用一些更直观的手段来描述优先级,于是有了优先级表 P
运算符 | + / - | * / / |
---|---|---|
优先级 | 0 | 1 |
根据上面的情况总结归纳:
- 如果扫描到运算目,则直接放入输出队列
- 如果扫描到运算符,则根据情况不同有三种情况:
- 栈为空栈,则直接压入
- 栈顶元素优先级高于或等于被扫描的运算符,则弹出栈顶至输出队列。被扫描运算符与新栈顶继续比较,直到被扫描运算符被压入栈内。(优先级高先运算规则和从左到右规则)
- 栈顶元素优先级低于被扫描的运算符,则将被扫描的运算符压入栈内。
当中缀表达式被扫描完以后,后缀表达式就算是完成了。
但上述方法还可以简化,单独运行一个函数去判断栈空过于费劲,我们可以放入一个优先级最低的作为栈底。
新优先级表:
运算符 | # | + / - | * / / |
---|---|---|---|
优先级 | 0 | 1 | 2 |
这样比较情况就只有两种,相对省事了一点。
带括号的四则运算
括号的引入改变了原有的计算顺序,所以需要进行一定修改。
括号内的算术内容要优先计算,所以从这个角度来讲,括号内的优先级高于括号外的优先级。
举例:
a+b*(c+d/e)-f
按照之前的算法逻辑,我们可以直接跳转到下面状态
当前扫描:
(
输出队列:ab
栈:#
+
*
- 由于括号优先级要高于
*
/
,所以括号得以入栈。 - 扫描 c ,放入输出队列
当前扫描:c
输出队列:abc
栈:#
+
*
(
- 扫描
+
,此时栈顶为(
优先级高于+
,如果按照之前优先级设计,加号将无法入栈。所以我们要对优先级设计进行修改。 为此我们引入栈内优先级和栈外优先级。
- 栈内优先级(in stack priority,简写为:isp):当运算符在栈内时所拥有的优先级。
(
应持有最低的优先级才能让后续其他运算符入栈。#
除外。- 栈外优先级(in coming priority,简写为:icp):当运算符在栈外时所拥有的优先级。
(
应持有最高的优先级才能让自己得以入栈
运算符 | # | ( | + / - | * / / |
---|---|---|---|---|
栈内优先级(isp) | 0 | 1 | 2 | 3 |
栈外优先级(icp) | 0 | 4 | 2 | 3 |
- 因为
(
的栈内优先级低于+
的栈外优先级,所以+
得以入栈 - 扫描 d
当前扫描:d
输出队列:abcd
栈:#
+
*
(
+
- 扫描
/
当前扫描:
/
输出队列:abcd
栈:#
+
*
(
+
/
- 扫描 e
当前扫描:e
输出队列:abcde
栈:#
+
*
(
+
/
- 扫描
)
,此时按照后缀表达式的规则,应该将/
弹出。所以)
的栈外优先级应该低于或等于/
的栈内优先级。 - 同理,
)
的栈外优先级应该低于或等于+
的栈内优先级。
当前扫描:
)
输出队列:abcde/+
栈:#
+
*
(
- 此时栈顶为
(
,后缀表达式中没有括号一说,所以(
被弹出,但不被放入输出队列。 - 至此,本括号内的所有算式结束,如果放任
)
继续弹出其他运算符,则有可能使后缀表达式发生错误(双层括号的情况,自己推演)。因此,必须将(
)
比较的结果与)
和其他运算符比较的结果区分开来。
最终的运算符优先级表
运算符 | # | ( | + / - | * / / | ) |
---|---|---|---|---|---|
栈内优先级(isp) | 0 | 1 | 3 | 5 | |
栈外优先级(icp) | 0 | 6 | 2 | 4 | 1 |
至此,只有括号匹配时,被扫描的栈外优先级
和栈顶栈内优先级
相等。除此之外,当被扫描的栈外优先级
高于栈顶栈内优先级
时,栈外元素入栈,反之,栈顶元素弹出并继续比对。
最终后缀表达式:abcde/+*+ -
总结
算法详述:
- 从左往右扫描中缀表达式
- 如果扫描到数字,加入后缀表达式
- 如果扫描到运算符
- icp(‘被扫描运算符’) > isp(‘栈顶运算符’),被扫描运算符入栈;继续扫描中缀表达式。
- icp(‘被扫描运算符’) < isp(‘栈顶运算符’),栈顶运算符弹出,加入后缀表达式;被扫描运算符继续比较新栈顶。
- icp(‘被扫描运算符’) == isp(‘栈顶运算符’),栈顶元素弹出,不加入后缀表达式;继续扫描中缀表达式。
运算符优先级表
运算符 | # | ( | + / - | * / / | ) |
---|---|---|---|---|---|
栈内优先级(isp) | 0 | 1 | 3 | 5 | |
栈外优先级(icp) | 0 | 6 | 2 | 4 | 1 |