前言
我第一次接触这三种表达式是在数据结构课程的二叉树遍历部分,所以下面一部分内容是从二叉树遍历角度来解释这三种表达式。
基于三种表达式特点的定义
定义
- 前缀表达式:在前缀表达式中,操作符位于两个操作数之前,操作数从从左到右顺序出现。
- 中缀表达式:在中缀表达式中,每个二元操作符出现在左操作数之后,右操作数之前。
- 后缀表达式:在后缀表达式中,操作符跟在两个操作数之后,操作数从左到右出现。
示例
| 中缀 | 前缀 | 后缀 |
|---|---|---|
| a*b+c/d | +*ab/cd | ab*cd/+ |
| a+b+c+d | ++++abcd | ab+c+d+ |
| a+b*c | +a*bc | abc*+ |
解释
- 首先中缀表达式就是我们在数学课上正常学到的数学表达式。
- 注意在 前缀表达式 和 后缀表达式 定义中两个操作数这一概念。对于数学表达式 a+b*c 来说,+ 号的左操作数是a,+ 号的右操作数不是b,而是 b*c 的积,所以依据前缀表达式的定义,+ 号应在 a 和 b*c 之前,而 b*c 又是一个数学表达式,所以 + 号应在a 和 b*c 的前缀表达式之前。又因为操作数从从左到右顺序出现,且 b*c 的结果就是一个操作数,所以 a 和 b*c 是都属于操作数,所以 a 和 b*c 的中缀表达式应从左到右顺序出现。后缀表达式同理。
基于二叉树定义
定义
- 对一棵数学表达式树分别进行中序、前序和后序遍历,结果便是表达式的中缀、前缀和后缀形式。
- 前序遍历:先访问节点,再访问节点的左子树,再访问节点的右子树
- 中序遍历:先访问节点的左子树,再访问节点,再访问节点的右子树
- 后序遍历:先访问节点的左子树,再访问节点的右子树,再访问节点
解释
- 数学表达式树可以说就是存储了一个数学表达式的二叉树,在数学表达式树中,左操作数是操作符的左子树,右操作数是操作符的右子树。
- 因为二叉树有三种遍历方式,可以得出三种遍历结果。一个数学表达式树在经过三种遍历后,返回的结果是不同的,对不同的结果有不同的计算方式。
- 在本文中我们不需要关心数学表达式树是如何创建的,也不太需要关心它对应的原数学表达式是什么,因为一个数学表达式可以对应多棵数学表达式树,一棵数学表达式树在中序遍历下也可能会对应多个不同的数学表达式。
示例
中缀表达式的歧义
算术表达式树如下:
中序遍历结果:x+y*z
而显然树对应的表达式还可能是:(x+y)*z。这样就出现了歧义。
注意:前序、中序和后序遍历都只是单纯的按照一定的顺序输出树中的节点元素。
前序遍历结果:*+xyz 等价的中缀表达式为 (x+y)*z
后序遍历结果:xy+z* 等价的中缀表达式为 (x+y)*z
前序和后序遍历不会出现歧义。
三种表达式的计算方法
前缀表达式
- 求值思路
从右至左扫描前缀表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对他们做相应的计算,并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果。 - 示例
算数表达式的中缀形式:(1+2)*3-4
对应的前缀表达式形式:-*+1 2 3 4
(1)从右至左扫描前缀表达式,将4、3、2、1依次入栈
(2)遇到 + 运算符,弹出1和2,计算的结果为3,再将3压入栈。
(3)扫描到*运算符,弹出3和3,计算结果为9,再将9压入栈。
(4)最后扫描到 - 运算符,弹出9和4,计算9-4得结果5,5入栈。
(5)扫描完毕,栈中的数据5即为最后结果。、
中缀表达式
可以先处理优先级高的运算符,再处理优先级低的运算符。
因为中缀表达式的歧义,一般不使用中缀表达式进行计算。
后缀表达式
- 求值思路
从左到右扫描后缀表达式,遇到数字时,将数字压入栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算,然后将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果。 - 示例
算数表达式的中缀形式:(1+2)*3-4
对应的后缀表达式形式:1 2+3*4-
(1)从左至右扫描后缀表达式,将1和2压入堆栈。
(2)遇到+运算符,弹出栈中的1和2(先入后出),计算1+2的值,得结果3,将3入栈
(3)将3入栈
(4)遇到运算符,弹出栈中3和3,计算3*3=9,将9入栈。
(5)将4入栈
(6)遇到-运算符,弹出栈中4和9,计算9-4=5,将5入栈。
(7)扫描结束,栈中即为算式最终结果。