(1)给表达式加括号(2)创建解析树表达式(3)树的后序遍历,生成后缀表达式(4)后缀表达式求值。经过前面几个步骤的洗礼,相信你内心充满了喜悦和兴趣。现在我们只需要将后缀表达式计算输出即可,然而可怕的是:-$
不借助树我们也能轻松实现上述过程。这种方法便是逆波兰表示法(Reverse Polish Network)。我们将着重介绍这一神奇的方法。
不幸的是,笔者的博客将不会有完整代码(示范代码除外),所有代码均已上传到码云上。如果时间和精力允许的话,强烈建议你根据思路,手动写一遍,相信你会感觉到全身毛孔舒张而不是想砸电脑的快感。
好了,按照惯例,我们先介绍一些术语。
栈: 存储数据的结构,讲究“先进后出”,即最先进栈的数据,最后出栈;有顺序存储和链式存储两种。
队列:存储数据的结构,它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
我们建立一个堆栈Stack()(用于存放运算符)和一个Queue()(用于存放后缀表达式)。思路是这样的,我们依次遍历中缀表达式(可以不带括号),记为i。
(1)遇到操作数直接输出到队列中。
(2)遇到'('则直接压入栈,因为它的优先级最小。
(3)遇到')',则开始出栈,将出栈的元素依次加入到队列中,直到遇到'('为止。
(4)遇到运算符,则比较复杂了。还好,你是一个心细如丝的人,你不会放过一个可能导致程序漏洞的bug。我们希望队列前面是最先被计算(优先级最高)的式子,比如1+(2+3)*4-5,我们得到后缀表达式为123+4*+5-。因此我们必须把i和栈顶元素作比较:如果i优先级大于栈顶元素,则可以直接入栈(如果栈是空的,我们得往栈压入一个运算符,不用比较);否则,将Stack出栈,输出到队列中,直到i优先级大于栈顶或者栈空为止。
(5)完成遍历后,若栈内还有元素,将它们依次输出到队列即可。但需要注意的是,我们此时将队列输出是一个逆序的后缀表达式。
如果你觉得理解起来比较吃力,我将画出这个过程,(人们总是喜欢配图的算法博客:-^),以1+(2+3)*4-5为例:
相信结合上述图,你已经熟悉了这种RPN。下面讲解如何计算后缀表达式。
从左到右遍历后缀表达式,我们准备一个数字栈s_num
(1)若遇到数字则压入s_num。
(2)遇到 运算符则,弹出s_num最上面两个元素,与操作符一起运算。将计算的结果重新压入栈。这一步很容易被未来的软件工程狮忽视。如此便计算出后缀表达式的结果。当然,遇到原理性问题比如1/0和log0和0^0,你的程序应该也能抛出相应的异常。
实现上述算法的好处是,你可以将二叉树得到的结果和RPN法得到的结果进行对比,如果两者不一样,一定是你的树出了问题。要么是给表达式加括号出了问题,要么是表达式解析树建立出了问题。笔者检测出无数bug也是这个原因。
下面是笔者一些测试数据,你在我的码云中也能看到:
编号 | 测试数据 | TREE | TREELESS(RPN) |
1 | 1 | 1.0 | 1.0 |
2 | 1+1 | 2.0 | 2.0 |
3 | 1+1*3 | 4.0 | 4.0 |
4 | 5/(1+5/6*3-3%2)*0.5+2/3+(2+3)/3 | 3.333333333333333 | 3.333333333333333 |
5 | 2^5 | 32.0 | 32.0 |
6 | 2%5^4 | 2.0 | 2.0 |
7 | (((3*(4+(66.6/6)))+(7*(9+(((4+2)%2)/2))))+(((3/2)-1)/0.5))/(5^2) | 4.372 | 4.372 |
8 | 4.2/(-3.5*+2+2^-2.2/4*5%6)+3-(-4.5) | 6.875738797245451 | 6.875738797245452 |
9 | 1/0 | RAISE ERROR | RAISE ERROR |
10 | 1/(2-1) | 1 | 1 |
11 | (5/6+1)/0.5^5+2 | 60.66666666666667 | 60.66666666666667 |
12 | (5/0.5^5%2*3+1)/0.5^5%2*3+1 | 1.0 | 1.0 |
参考资料:https://www.cnblogs.com/icodes8238/p/12243275.html
希望笔者的博客能对你有帮助,如果有问题,欢迎留言,或者email durant2019@sina.com! 当然你给我Gitee ,Fork&Star一下,笔者也是不胜感激的!