逆波兰表达式是一种十分有用的表达式,它将复杂表达式转换为可以依靠简单的操作得到计算结果的表达式。例如(a+b)*(c+d)转换为ab+cd+*d+。
它的优势在于只用两种简单操作,入栈和出栈就可以搞定任何普通表达式的运算。其运算方式如下:
如果当前字符为变量或者为数字,则压栈,如果是运算符,则将栈顶两个元素弹出作相应运算,结果再入栈,最后当表达式扫描完后,栈里的就是结果。
将一个普通的中序表达式转换为逆波兰表达式的一般算法是:
(1)首先构造一个运算符栈,此运算符在栈内遵循越往栈顶优先级越高的原则。
(2)读入一个用中缀表示的简单算术表达式,为方便起见,设该简单算术表达式的右端多加上了优先级最低的特殊符号"#"。
(3)从左至右扫描该算术表达式,从第一个字符开始判断,如果该字符是数字,则分析到该数字串的结束并将该数字串直接输出。
(4)如果不是数字,该字符则是运算符,此时需比较优先关系。
做法如下:将该字符与运算符栈顶的运算符的优先关系相比较。如果,该字符优先关系高于此运算符栈顶的运算符,则将该运算符入栈。倘若不是的话,则将栈顶的运算符从栈中弹出,直到栈顶运算符的优先级低于当前运算符,将该字符入栈。
(5)重复上述操作(1)-(2)直至扫描完整个简单算术表达式,确定所有字符都得到正确处理,我们便可以将中缀式表示的简单算术表达式转化为逆波兰表示的简单算术表达式。
其中运算符优先级如下:
*/:4
+-:3
(:2
):1
中缀表达式到后缀表达式的转换
要把表达式从中缀表达式的形式转换成用后缀表示法表示的等价表达式,必须了解操作符的优先级和结合性。优先级或者说操作符的强度决定求值顺序;优先级高的 操作符比优先级低的操作符先求值。如果所有操作符优先级一样,那么求值顺序就取决于它们的结合性。操作符的结合性定义了相同优先级操作符组合的顺序(从右 至左或从左至右)。
转换过程包括用下面的算法读入中缀表达式的操作数、操作符和括号:
1. 初始化一个空堆栈,将结果字符串变量置空。
2. 从左到右读入中缀表达式,每次一个字符。
3. 如果字符是操作数,将它添加到结果字符串。
4. 如果字符是个操作符,弹出(pop)操作符,直至遇见开括号(opening parenthesis)、优先级较低的操作符或者同一优先级的右结合符号。把这个操作符压入(push)堆栈。
5. 如果字符是个开括号,把它压入堆栈。
6. 如果字符是个闭括号(closing parenthesis),在遇见开括号前,弹出所有操作符,然后把它们添加到结果字符串。
7. 如果到达输入字符串的末尾,弹出所有操作符并添加到结果字符串。
逆波兰表达式又称后缀式,是波兰逻辑学家J.Lukasiewicz于1929提出的一种后缀表达运算式的方 法。区别于我们所熟悉的中缀式,后缀式的特点 就是运算量在前,运算符在后,如a+b表达为ab+。这种后缀表达式非常方便去掉运算符优先级的影响与括号,甚至是单目运算符:
1. a*b+c*(d+e) => ab*cde+*+
2. -a+-b*+c => a!b!c#*+ (注:为区别与减法运算与加法运算,这里用!表示负号,#表示正号)
那么计算机怎样通过后缀式来进行运算呢?这里首先假设读取分析表达式的准备工作都已经做好了,那么首先需要做的是把表达式转换成后缀式,也就是逆波兰表达式的构建过程。
构建器由两个主要组件组成,一个是目标表达式的存储器,另一个是一个符号栈。与源表达式的扫描顺序一样,存储器是从左向右储存数据的,而符号栈则遵守后进先出的原则:
* 读入一个数据
1. 如果是单目运算符,直接入符号栈;
2. 如果是运输量,则直接写入存储器;检查符号栈顶是否有单目运算符,有的话则全部出栈,并写入存储器;
3. 如果是左括号"(",则直接入符号栈;
4. 如果是右括号")",则弹出符号栈数据,写入存储器,直到左括号弹出;
5. 如果是普通运算符,则与栈顶符号比较优先级,若大于栈顶优先级,则入栈;否则弹出栈顶符号并写入存储器,直到栈顶符号的运算优先级较小为止;
6. 如果是结束符(表示表达式已全部读完),则符号栈全部弹出并写入存储器,否则读取数据进入下个过程。
此外还有一些处理的技巧,比如定义一个优先级最低的运算符作为表达式结束的标志,在符号栈里首先加入一个结束标志,那么表达式读完时则自动弹出栈中所有符号,并写入存储器结尾表示成功。
下面用一个表格表示构建的过程:
Token | Expression | Dest Data | LIFO stack | Description |
| a*b+c*(d+-e)# |
| # | # is Ending Sign |
a | *b+c*(d+-e)# | a | # | Value -> Data |
* | b+c*(d+-e)# | a | #* | * > # |
b | +c*(d+-e)# | ab | #* | Value -> Data |
+ | c*(d+-e)# | ab* | # | + < * |
| c*(d+-e)# | ab* | #+ | + > # |
c | *(d+-e)# | ab*c | #+ | Value -> Data |
* | (d+-e)# | ab*c | #+* | * > + |
( | d+-e)# | ab*c | #+* | LPar( -> Stack |
d | +-e)# | ab*cd | #+*( | Value -> Data |
+ | -e)# | ab*cd | #+*(+ < | + > ( |
! | e)# | ab*cd | #+*(+! | ! -> Stack |
e | )# | ab*cde! | #+*(+ | Value -> Data, Pop ! |
) | # | ab*cde!+ | #+* | RPar) -> Pop Until ( |
# |
| ab*cde!+*+# |
| # -> Pop Until # |
接下来是计算的过程。计算的时候除了刚才构建的数据外,还需要另外一个数据栈。首先是从左至右扫描数据段,如果读出的是数据则压入数据栈,遇到符号时从数据栈中弹出相应的数据进行运算,再把结果压回数据栈。依然拿上面的结果做表格列示:
Token | Expression | Data Stack | Compute | Description |
| ab*cde!+*+# |
|
|
|
a | b*cde!+*+# | a |
| Value -> Stack |
b | *cde!+*+# | ab |
| Value -> Stack |
* | cde!+*+# |
| a*b | Pop a, b |
| cde!+*+# | m |
| set m = a*b |
c | de!+*+# | mc |
| Value -> Stack |
d | e!+*+# | mcd |
| Value -> Stack |
e | !+*+# | mcde |
| Value -> Stack |
! | +*+# | mcd | !e | Pop e |
| +*+# | mcdn |
| set n = !e |
+ | *+# | mc | d+n | Pop d, n |
| *+# | mco |
| set o = c+n |
* | +# | m | c*o | Pop c, o |
| +# | mp |
| set p = c*o |
+ | # |
| m+p | Pop m, p |
| # | r |
| set r = m+p |
# |
| r | check | Accept! |
这样,返回结果就是栈中唯一的数据,我们完成了逆波兰表达式的全部计算过程。