前几天有个面试题目:计算字符串"1 + (5 - 2) * 3",结果为10,不能用eval()。今天介绍一下用压栈的方法解一解这个题目,事实上我们的计算器原理也是如此。
1 分析题目
(1)如果计算“1+2”这种两个数之间的运算,比较简单,可直接将“字符数字”1,2分解出来,强制转换为float类型,然后根据中间的运算符加减乘除就行。这题难在需要再复杂的算式中考虑运算符有优先级。
(2)通常我们在计算的时候,实际上也是不断进行两个数之间运算,并将算完的结果再和其他数进行运算。比如“1 + 2 + 4”,第一步先算出1+2=3之后,再用算出的结果3和4相加,得到最终结果7。
(3)如果我们能够将算式“1+2+4”,看做是一个处理好的列表:
将字符串算式 "1+2+4" 处理成: ['1', '+', '2', '+', '4']
那么我们可以通过压栈的方式计算出结果。首先设置两个列表(栈),分别存放 数字 和 运算符,然后遍历 ['1', '+', '2', '+', '4']:
遍历 处理过的算式列表:['1', '+', '2', '+', '4']
第一次:
得到数字'1', 转换成float, 放入数字栈:
数字栈: [1.0, ]
运算符栈: [ ]
第二次:
得到运算符'+',放入运算符栈:
数字栈:[1.0, ]
运算符栈:['+', ]
第三次:
得到数字'2',转换成float, 放入数字栈:
数字栈:[1.0, 2.0]
运算符栈:['+', ]
第四次:
得到运算符'+', 此时应注意:
运算符栈的最后一位也是'+'号, 现在又来了一个'+'号,说明相邻两个运算符的优先级别是一样的。
既然优先级别是一样的,四则运算法则告诉我们应该从左往右计算对吧?所以,此处不再一味地将运算符'+'入栈。而是:
(1)弹出数字栈中的最后两位数字,即 2.0 和 1.0 ;
(2)弹出运算符栈中的最后一个运算符'+';
(3)将弹出的数字和运算之间进行计算,即计算 2.0 + 1.0 = 3.0;
(4)将3.0放入数字栈,代替之前的 1.0 和 2.0;
即:
数字栈:[3.0, ]
运算符栈:[ ]
别忘了,我们第四次得到的运算符'+'号,此时,
如果运算符栈中,弹出上一次运算过的运算符'+'之后,还有别的运算符,
那么我们还应该将运算符栈的最后一个运算符 和 本次得到的运算符 '+' 进行比较,判断是否是同一级别。
如果同一级别还得继续弹栈,继续运算。不在同一级别那就应该将运算符入栈。
而现在,我们的运算符栈已经空了,那么应该把运算符'+'放入运算符栈,即:
数字栈:[3.0, ]
运算符栈:['+', ]
这样第四次才算大功告成。
第五次:
得到数字'4',转换成float, 放入数字栈:
数字栈:[3.0, 4.0]
运算符栈:['+', ]
至此我们已经遍历完算式列表:['1', '+', '2', '+', '4'],但在数字栈和运算符栈中还有元素。
那么我们应该依次弹出最后两个数字4.0 和3.0,以及最后一个运算符'+',然后进行运算,得到7.0,并代替原来数字栈中的4.0 和3.0,即:
数字栈:[7.0, ]
运算符栈:[ ]
最后得到的最终结果就是数字栈中的第一位元素:7.0。
通过描述计算 "1+2+4" 的过程,我们总结出这一方法计算的两个重点:
第一个重点:把算式处理成列表形式。如:'-1-2*((-2+3)+(-2/2))' 应该处理成:['-1', '-', '2', '*', '(', '(', '-2', '+', '3', ')', '+', '(', '-2', '/', '2', ')', ')'] 。
第二个重点:建立两个栈,数字栈和运算符栈。遍历算式列表,(从前往后取出列表中的元素),将数字放入数字栈,将运算符放入运算符栈。但是,需要通过运算符栈中的最后一个运算符 与 当前拿到的运算符 比较,判断出应该弹栈进行运算还是直接入栈。
2 总结算法
通过1中的分析我们大致可以整理出如下算法:
1 将算式整理成列表formula_list。
2 循环[为方便描述,我们把此处循环叫循环1],依次取出列表中的元素 e (element缩写)。
if e 是数字:
加入数字栈num_stack,获取下一个元素e