【原创】基于逆波兰式法的数学表达式计算算法举例演示
基于逆波兰式法的数学表达式计算算法
基于逆波兰式法的数学表达式计算算法
我在上一篇文章中给出了计算规则,但没有举例,这里我举一个具体的例子做说明,方便大家理解。
上篇文章的地址:http://www.cnblogs.com/tangqs/archive/2011/11/03/2234715.html
里面有Delphi7版的源代码和按此方法开发的表达式计算器。
这里先给出运算符的优先级,表中0级优先级最低,7级最高,如下:
优先级 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
运算符 |
# |
( |
, |
+- |
* / |
@ ~ |
!% ^ |
) |
说明 |
表达式结束符 |
左括弧 |
函数参数连接符 |
加减 |
乘除 |
正负 |
阶乘,百分数,求幂 |
右括弧 |
下面举一个简单的例子,计算表达式:2+(8+4^2)*3
主要分两步,第一步生成逆波兰表达式,第二步计算逆波兰表达式,如下:
第一、生成逆波兰表达式
首先构建一个存储器,另一个符号栈,存储器是从左向右储存数据,而符号栈则遵守后进先出的原则,计算表达式按从左至右的顺序扫描。
* 读入一个数据(重点注意:数值与函数名非单个字符,需要做判断处理)
1. 如果是左单目运算符或者函数名,直接入符号栈;比如 正负号 ~ @ max sin
2. 如果是右单目运算符,直接入存储器栈;比如 阶乘!与百分号%
3. 如果是运输量,则直接写入存储器;检查符号栈顶是否有单目运算符,有的话则全部出栈,并写入存储器;
4. 如果是左括号"(",则直接入符号栈;
5. 如果是右括号")",则弹出符号栈数据,写入存储器,一直到左括号弹出(左括弧直接丢弃,不写入存储器),再检查栈顶是否为左单目运算符或者函数名,是的话继续弹出,直到遇到双目运算符;
6. 如果是双目运算符,则与栈顶符号比较优先级,若大于栈顶优先级,则入栈;否则弹出栈顶符号并写入存储器,直到栈顶符号的运算优先级较小为止;
7.如果是函数参数的连接逗号“,”时,则弹出符号栈数据,直到遇到左括弧 ( 或者逗号,为止,再将逗号,入符号栈;
8.如果是结束符(表示表达式已全部读完),则符号栈全部弹出并写入存储器,否则继续按以上规则读取下一个数据;
下面将逐步介绍逆波兰式生成过程:
表达式按从左至右扫描,标记为红色的字符为当前步骤扫描的字符:
0、预处理:为表达式添加一个结束标示符“#”,扫描时按双目运算符考虑,
处理后表达式为:2+(8+4^2)*3#
1、2+(8+4^2)*3#:扫描“2”,按规则3,将运算量 2压入存储器,如下:
存储器: |
2 |
|
|
|
|
|
|
|
|
|
|
符号栈:暂空
|
2、2+(8+4^2)*3#:扫描“+”,按规则6,将加号压入符号栈,如下:
存储器: |
2 |
|
|
|
|
|
|
|
|
|
|
符号栈:
+ |
3、2+(8+4^2)*3#:扫描“(”,按规则4,将右括弧直接压入符号栈,如下:
存储器: |
2 |
|
|
|
|
|
|
|
|
|
|
符号栈:
( |
+ |
4、2+(8+4^2)*3#:扫描“8”,按规则3,将运算量 8压入存储器,如下:
存储器: |
2 |
8 |
|
|
|
|
|
|
|
|
|
符号栈:
( |
+ |
5、2+(8+4^2)*3#:扫描“+”,按规则6,优先级高于(,直接压入符号栈,如下:
存储器: |
2 |
8 |
|
|
|
|
|
|
|
|
|
符号栈:
+ |
( |
+ |
6、2+(8+4^2)*3#:扫描“4”,按规则3,将运算量 4压入存储器,如下:
存储器: |
2 |
8 |
4 |
|
|
|
|
|
|
|
|
符号栈:
+ |
( |
+ |
7、2+(8+4^2)*3#:扫描“^”,按规则6,优先级高于+, 直接压入符号栈,如下:
存储器: |
2 |
8 |
4 |
|
|
|
|
|
|
|
|
符号栈:
^ |
+ |
( |
+ |
8、2+(8+4^2)*3#:扫描“2”,按规则3,将运算量2压入存储器,如下:
存储器: |
2 |
8 |
4 |
2 |
|
|
|
|
|
|
|
符号栈:
^ |
+ |
( |
+ |
9、2+(8+4^2)*3#:扫描“)”,按规则5,依次弹出 ^ + 压入存储器,同时弹出符号栈左括弧,丢弃。如下:
存储器: |
2 |
8 |
4 |
2 |
^ |
+ |
|
|
|
|
|
符号栈:
+ |
10、2+(8+4^2)*3#:扫描“*”,按规则6,优先级高于+, 直接压入符号栈,如下:
存储器: |
2 |
8 |
4 |
2 |
^ |
+ |
|
|
|
|
|
符号栈:
* |
+ |
11、2+(8+4^2)*3#:扫描“3”,按规则3,将运算量3压入存储器,如下:
存储器: |
2 |
8 |
4 |
2 |
^ |
+ |
3 |
|
|
|
|
符号栈:
* |
+ |
12、2+(8+4^2)*3#:扫描“#”,按规则6,将符号栈优先级高的符号弹出压入存储器,,然后将#号压入符号栈,如下:
存储器: |
2 |
8 |
4 |
2 |
^ |
+ |
3 |
* |
+ |
|
|
符号栈:
# |
至此,表达式扫描结束,得到的存储器数值顺序就是逆波兰表达式。
第二、计算逆波兰表达式
按从左至右扫描数据存储器,扫描规则如下:
a、如果读出的是数据则压入计算中间值存储栈;
b、遇到单目运算符号就从计算中间值存储栈弹出一个数据进行运算,再把结果压回计算中间值存储栈;
c、遇到双目运算符号就从计算中间值存储栈弹出两个数据进行运算,再把结果压回计算中间值存储栈;这里需要注意减法与除法以及求幂等计算的顺序,第一次弹出的值作为减数和除数,第二次弹出的值作为被减数和被除数。
d、遇到逗号,就从计算中间值存储栈弹出两个数据用“,”连接起来直接将数值字符串压入计算中间值存储栈,不做计算。比如 12 13 ,压入13,12
e、遇到函数,弹出计算中间值存储栈的相关数据调用函数进行计算;
具体计算步骤
这里除了前面的存储器外,还需要一个计算中间值存储栈。
然后从左至右扫描存储器,下面将给出计算步骤。
存储器: |
2 |
8 |
4 |
2 |
^ |
+ |
3 |
* |
+ |
1、扫描“2”,按规则a,弹出后压入计算中间值存储栈,如下:
存储器: |
|
8 |
4 |
2 |
^ |
+ |
3 |
* |
+ |
计算中间值存储栈:
2 |
2、依次扫描“8”,“4”,“2”,都遵循规则a,弹出后压入计算中间值存储栈,如下:
存储器: |
|
|
|
|
^ |
+ |
3 |
* |
+ |
计算中间值存储栈:
2 |
4 |
8 |
2 |
3、扫描“^”,按规则c,弹出2、4,按求幂计算 4^2的结果16压入中间值存储栈,如下:
存储器: |
|
|
|
|
|
+ |
3 |
* |
+ |
计算中间值存储栈:
16 |
8 |
2 |
4、扫描“+”,按规则c,弹出16、8,按求和计算结果24压入中间值存储栈,如下:
存储器: |
|
|
|
|
|
|
3 |
* |
+ |
计算中间值存储栈:
24 |
2 |
5、扫描“3”,按规则a,弹出数值3后压入计算中间值存储栈,如下:
存储器: |
|
|
|
|
|
|
|
* |
+ |
计算中间值存储栈:
3 |
24 |
2 |
6、扫描“*”,按规则c,弹出3、24,按相乘计算结果72压入中间值存储栈,如下:
存储器: |
|
|
|
|
|
|
|
|
+ |
计算中间值存储栈:
72 |
2 |
7、扫描“+”,按规则c,弹出72、2,按求和计算结果74压入中间值存储栈,如下:
存储器: |
|
|
|
|
|
|
|
|
|
计算中间值存储栈:
74 |
至此,存储器扫描完毕,也即计算完毕,
中间值存储栈中保存的74即为表达式的计算结果。
对于左右单目运算符的计算也是一样的,按上面的规则一样可以计算,对于函数的计算有些不一样,需要编写一些计算方法,具体大家可以看看我源代码里面的处理方法。
源代码下载地址:http://www.cnblogs.com/tangqs/archive/2011/11/03/2234715.html