通过后缀表达式求表达式的值
作者:互联网
通过后缀表达式求表达式的值
知识点:树的前中后序遍历(可以参考AK宝典),后缀表达式(逆波兰式),中缀转后缀,后缀表达式求值
引言:
对于一个数学表达式,比如说 1-(2+3/4)*5=?可以很容易地人工计算出结果。
然而如果想要用计算机求这样表达式的值似乎有一点麻烦,因为计算机不太方便处理运算符的优先级,尤其是括号对优先级的影响。
因此,想要用计算机求表达式的值,最好要找到一种方法可以排除括号,运算符优先级的干扰。
一.表达式与树:
事实上,任何一个数学表达式都可唯一转化为一棵树,比如说:1-(2+3/4)*5=?可以转化为如下一棵二叉树:
树的特性:
可以看出,这棵二叉树的每一个叶子节点都是一个数,而每两个节点的父节点都是一个运算符,比如最右边子树3,4,/,它代表3/4
如果能拿到这样的一颗二叉树,求上述表达式的值就很简单了,我们只需要先算出子树3/4=0.75,再计算子树2+0.75=2.75,再计算子树5*2.75=13.75,最后计算根节点处1-13.75=-12.75即可求出表达式的值
不难发现,这颗二叉树的中序遍历结果为:1-2+3/4*5
,也就是原表达式去掉括号的样子。
因此,原数学表达式也被称为中缀表达式,对这棵树后序遍历得到的结果称为后缀表达式
树的求法:
而这样的树的求法也很很简单,我们只需要找出原表达式中最先计算的运算( 3/4
),把它作为一颗子树,然后我们就可以把这个运算(3/4
)视为一个整体,找出原式中其次优先的运算(2+3/4
)和之前的子树组成一颗更大的树,直至把所有运算都处理完,就可以得出一整棵树
不过,对于计算机而言,无论是求树的过程,还是通过树求值的过程都还是有些复杂,需要进一步简化
二.使用后缀表达式求值:
首先,这颗树对应的后缀表达式为 1 2 3 4 / + 5 * -
,不难看出,后缀表达式不含括号,数字的排列顺序与原式相同,原式中优先级越高的运算符在该后缀表达式中越先出现。
因此,一定存在某种方案能够使得计算机在处理一个后缀表达式时能够先把运算优先级高的运算计算出来,使其能够用于后续的低优先级运算。
而对于中缀表达式,由于运算级高的运算不一定排在前面,因此计算机必须反复扫描中缀表达式,先算优先级高的,再算优先级低的运算,这就是后缀表达式更加易于求值的原因。
具体而言:
我们依次处理后缀表达式中的每一个数字或符号a[i]:
- 如果a[i]是一个数字,就直接将其压入栈s中
- 如果a[i]是一个运算符,则将栈s顶端的第一个元素a,第二个元素b分别从栈中取出并清除,计算 c=b运算a (比如
c=b-a
),然后再将c压入栈中,进行后续操作
对于后缀表达式1 2 3 4 / + 5 * -
,运算过程如下:
后缀表达式 | 栈(从底到顶) | 运算过程 |
1 2 3 4 / + 5 * - | 1 | 1入栈 |
1 2 3 4 / + 5 * - | 1 2 | 2入栈 |
1 2 3 4 / + 5 * - | 1 2 3 | 3入栈 |
1 2 3 4 / + 5 * - | 1 2 3 4 | 3入栈 |
1 2 3 4 / + 5 * - | 1 2 0.75 | 3,4出栈 3/4=0.75入栈 |
1 2 3 4 / + 5 * - | 1 2.75 | 2,0.75出栈 2+0.75=2.75入栈 |
1 2 3 4 / + 5 * - | 1 2.75 5 | 5入栈 |
1 2 3 4 / + 5 * - | 1 13.75 | 2.75,5出栈 2.75*5=13.75入栈 |
1 2 3 4 / + 5 * - | -12.75(答案) | 1 13.75出栈 1-13.75=-12.75入栈 |
不难看出,使用后缀表达式求值的过程其实本质上与通过子树逐步求值的过程相同
那么,要如何求后缀表达式呢?
三.中缀表达式转后缀表达式:
中缀表达式转后缀表达式可以借助两个栈来完成,设两个栈分别为s1,s2,我们从左到右遍历原本的中缀表达式,设当前遍历到的数字或符号为a[i]
- 如果a[i]为
数字
,那么直接压入s2中 - 如果a[i]为
运算符
,那么需比较a[i]和s1栈顶符号ch的优先级- 如果s1为空或ch为'(',则直接将a[i]压入栈s1中
- 如果a[i]优先级 > ch优先级,则直接将a[i]压入栈s1中
- 如果a[i]优先级 < ch优先级,则将ch压入s2中,并从s1中删除,再比较新的s1栈顶元素ch'与a[i]的优先级
- 如果a[i]优先级 = ch优先级,若ch与a[i]为左结合,则认为ch优先级 > a[i]优先级;若ch与a[i]为右结合,则认为ch优先级 < a[i]优先级
- 如果a[i]为左括号
'('
,那么直接压入s1中 - 如果a[i]为右括号
')'
,那么将s1中第一个左括号'('前的所有运算符依次压到到s2中,并从s1中删除,最后将s1中的这个左括号'('删除 - 如果a[i]为结束符
'='
,那么s1中剩余的全部运算符依次移到s2中,转换结束
完成后s2所存结果就是原式的后缀表达式,但是由于栈式先进后出的,所以s2栈顶元素依次输出所得其实式逆序的后缀表达式,需要进行反向处理
注:典型的左结合运算符有+-*/
,特点为a+b+c=(a+b)+c
典型的右结合运算符有^
(幂运算),特点为a^b^c=a^(b^c)
对于1-(2+3/4)*5=,运算过程如下:
原式 | 栈s1 | 栈s2 | 运算过程 |
1-(2+3/4)*5= | 1 | 1 入栈 | |
1-(2+3/4)*5= | - | 1 | - 入栈 |
1-(2+3/4)*5= | - ( | 1 | ( 入栈 |
1-(2+3/4)*5= | - ( | 1 2 | 2 入栈 |
1-(2+3/4)*5= | - ( + | 1 2 | + 入栈 |
1-(2+3/4)*5= | - ( + | 1 2 3 | 3 入栈 |
1-(2+3/4)*5= | - ( + / | 1 2 3 | / 入栈 |
1-(2+3/4)*5= | - ( + / | 1 2 3 4 | 4 入栈 |
1-(2+3/4)*5= | - | 1 2 3 4 / + | / + 移入s2,( 清除 |
1-(2+3/4)*5= | - * | 1 2 3 4 / + | * 入栈 |
1-(2+3/4)*5= | - * | 1 2 3 4 / + 5 | 5 入栈 |
1-(2+3/4)*5= | 1 2 3 4 / + 5 * - | * - 依次移入s2,结束 |
依次输出s2栈顶元素 -*5+/4321 ,是后缀表达式的逆序
四.完整代码如下:
//暂时不发....
标签:优先级,入栈,后缀,s1,通过,运算符,表达式 来源: https://www.cnblogs.com/STEllIAF0X/p/16166578.html