中缀表达式转后缀表达式(Stunting-yard algorithm)(poj2269)

算法:

Edsger Dijkstra 发明的 Stunting-yard algorithm。Dijkstra就是发明单源点最短路径那个算法的牛人。

为了简单,以加、减、乘、除这4个二元运算符来描述算法。掌握了思想之后,很容易扩展到对优先级更多、结合性更多变、操作数数目更灵活的表达式求值。


基础知识:

优先级:乘、除运算优先级相等,加、减运算优先级相等,乘除运算优先于加减运算。

结合性:结合性是在优先级相等的时候,用来决定运算顺序的。

是否可交换操作数:+、*是可交换的,-、/是不可交换的。(例:A+B=B+A;但是A-B!=B-A;)【算法并不交换操作数的位置,提到这个只为排除干扰】


中缀表达式和后缀表达式的区别:

     式子           运算规则运算符和操作数的位置(A-B为例)

中缀表达式: A*B+(C-D),可以有括号先乘除后加减,有括号先做括号A-B,操作符在A之后,B之前

后缀表达式: AB*CD-+,不包含括号按照运算符出现的先后顺序进行运算AB-,A之后是B,B之后是操作符


为什么要有后缀表达式:

上小学时学加减法的时候,使用的就是中缀表达式(列:9+11),然后慢慢式子变得复杂就出现了括号。

比如有两个计算题:(1)9-1+1(2)9-(1+1)

为了改变运算符的优先级,就使用了括号,而且是必须使用,才能使得(1)中先计算9-1=8.然后8+1=9;(2)中先计算1+1=2,然后9-2=7。

所以带括号的中缀表达式对人类来讲,非常的方便,但是计算机不是人,在处理带括号的表达式的时候非常不方便,于是便出现了后缀表达式。

使用后缀表达式则原题变为:(1)9 1 - 1 + (2)9 1 1 + -

两个特点:1、没有改变操作数的相对位置,出现顺序都是911。

    2、后缀表达式操作符出现的顺序就是实际进行运算的顺序,(1)中先-后+,(2)中先+后-。

最奇怪的一个地方:后缀表达式中操作符要使用的操作数在操作符前面,如91-,则表示9-1=8。


计算机如何计算后缀表达式:

如果你不明白‘栈’这个数据结构,请找资料学习了再来。

else,看看维基百科逆波兰式(后缀表达式):http://en.wikipedia.org/wiki/Reverse_Polish_notation

顺便提一点。苦逼的码农们,看资料的时候,能看懂英文,尽量看英文吧····理由就不多说了······


重点:中缀表达式转后缀表达式:

首先要说明的是,我并不打算把算法的详细流程说一遍,因为这些维基百科上都有:http://en.wikipedia.org/wiki/Shunting-yard_algorithm

我更不愿意直接把算法的c++实现贴上来随便说两句就不管了。因为代码网上实在太多,我写的也不好看。更加重要的是,任何人先自己有了想法,琢磨怎么解决问题,然后看了详细流程了解思想,然后实现了算法,在OJ上找到一个题,A了,才能收获。比如POJ2269就是一个对应中缀表达式转后缀表达式的题目。

我要写的是,我自己在这个过程中思考的过程,具体一点就是,为什么Dijkstra可以想到这个办法,我想不到,为什么我不仅想不到,而且看了Dijkstra的算法之后,一时半会还理解不了。Dijkstra在算法领域是天才,我是菜鸟。这是一个理由。但是这个我改变不了。所以我还是想写一写自己的思考过程,毕竟菜鸟也要生活。。。与各位共勉。

进入正轨之前再说一句闲话:我的语言组织和表达能力有限,另外我在学习理解算法的过程中遇到的认为有意义的问题,不一定你也觉得有意义,所以我会想到什么写什么。当你觉得我写的不对,或者很简单没有看的必要,或者你认为你根本就不是一个值得讨论的问题。请稍微鄙视下就直接跳过吧。

1、某个由中缀表达式给出的算式转化为后缀表达式之后,运算顺序可能不完全一样,但是运算结果不变。

例:中缀表达式A-B+C*D转化为后缀表达式为AB-CD*+。

中缀表达式按照先乘除后加减的运算顺序,应该是先计算C*D,然后计算A-B,最后计算(A-B)+(C*D)。

后缀表达式的运算顺序,先计算A-B,然后计算C*D,最后计算(A-B)+(C*D)。

但是运算结果不变,为什么?

先考虑下转换过程,按顺序读入A-B+C*D:

读入A 输出A 符号栈:NULL

读入- 输出A 符号栈:-(上面已经说过,后缀表达式中操作符出现的先后顺序就是实际进行运算的顺序,为什么我们此时将-号操作符入栈,保存起来,而不是直接输出为结果?因为式子还没读完,后面可能会遇到优先级更好的运算符,所以我们维护一个操作符栈,栈顶符号具有最高的运算优先级,从栈顶到栈尾,优先级逐渐降低)

读入B 输出AB 符号栈:-

读入+ 输出AB- 符号栈:+(之前栈顶符号是-号,和+号有一样的运算优先级,而且-号操作符是满足左集合性的。所以计算A-B+C这个运算式,我们应该先做-法,后做减法,于是我们将之前栈顶运算符-号输出为结果,然后将+号入栈)

理一下思绪:

任何时刻我们读入一个操作符的时候,都要和栈顶操作符的优先级进行比较(同优先级则比较结合性),而且不是比较一次,当栈顶操作符优先级更高时,这个操作符的运算顺序已经被确定,输出这个运算符到结果中,然后将读入的操作符与新的栈顶操作符进行比较,这个过程是循环的,次数不定。在这个过程结束了之后,我们将这个操作符入栈,因为这个操作符的实际运算顺序此时还不能确定,因为这个操作符的第二个操作数还没有读入,我们不知道这个操作数是否‘卷入’了优先级更高的运算符,或者括号。距离来说就是,当我们读入A+的时候,我们不知道之后的式子是A+B-C,还是A+B*C,所以当读入+号的时候,不能确定加号的运算顺序,因为加号的第二操作数是B,在A+B-C中B是B-C中减法的第一个操作数,-和+优先级一样,于是+法先做。但是在A+B*C中,B是B*C的第一个操作数,*比+优先级更高,于是*法先做。


最后一个问题:为什么读入一个操作符o1,如果栈顶操作符o2的优先级高于o1,此时o2就应该出栈,输出到结果中?(即o2操作符的运算顺序在式子没有读完整的情况下已经确定)

因为o2的两个操作数已经出现,两个操作数卷入的其他操作符的优先级都低于o2,故运算顺序已经确定。(留心以后此种问题多元运算符的变种问题)



根据前缀表达式可以构建出parse tree(语法树)

例:

前缀表达式: *+a**bc+def

前缀表达式的求解方法:从右往左,符号出现的顺序就是实际的执行顺序,每当遇到符号,就从右边依次取出两个操作数,进行运算后存入结果.

第一种方法是用栈来辅助计算,第二种方法就是用递归版本的函数.

递归版本:从左往右遍历表达式

如果遇到的是操作符:生成一个结点,递归的调用函数,分别将结点的左右孩子指向函数的返回值.

如果遇到的是操作数:生成一个结点.直接放回结点的指针.


这棵树有什么特征?

1.叶子结点都是操作数,中间结点都是操作符.

2.位于同一层的操作符执行先后顺序任意.最下层的操作符先执行,这层所有的操作符执行完了之后,才能执行上一层的操作符.

再回到三种遍历顺序的本质定义:

先序:根\左\右 中序:左\根\右 后序:左\右\根

得出结论,对这个语法树进行3种遍历,就可以分别得到前缀\中缀\后缀表达式.


也可以用abstract syntax tree的后序遍历来做infix到postfix的转换,等学到树的时候,再来把这个补充完整。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值