背景
表达式求解这个问题绝对是非常值得搞清楚的一个问题, 一方面在面试中经常会被问到(https://leetcode-cn.com/problems/basic-calculator/), 我就曾不止一次在面试中被问到类似的问题, 然而当时回答的都不理想; 另一方面, 在我们的工作中, 其实会遇到真实的问题需要类似的算法求解, 但是一般是改头换面出现的, 比如我最近遇到一个问题, 业务端传给我一个类似于这样的表达式:
a && b && !(c || d)
我希望对这个表达式做进一步的处理, 处理的第一步就需要把这个表达式给parse成语法树, 比如可以处理成如下的json格式:
{
"and": [
{
"and": [
"a",
"b"
]
},
{
"not": [
{
"or": [
"c",
"d"
]
}
]
}
]
}
这个问题虽然看起来和加减乘除的那种表达式不一样, 但其实本质是完全一样的.
可以把&&
看成*
, 把||
看成+
, 把!
看成-
, 于是上面那个布尔表达式就变成了:
a*b*-(c+d)
我们完全可以用处理这个代数表达式的方法处理那个布尔表达式.
可见, 这个题目值得我们研究一下[https://leetcode-cn.com/problems/basic-calculator/].
后缀表达式
本题需要对给定的字符串类型的表达式进行解析并计算, 一般涉及到这种要计算中缀表达式(也就是我们一般用的表达式, 运算符在操作数中间)的问题, 一个常用的思路是用shunting-yard算法转换为后缀表达式.
如 4+18/(9-3)
(中缀) => 4, 18, 9, 3, -, /, +
(后缀)
为什么要转化为后缀表达式呢? 因为转化为后缀表达式后利于我们写算法计算结果.直观来看, 中缀表达式是含有括号的, 而后缀表达式是没有括号的; 而且还有很重要的一点: 中缀表达式是考虑运算符优先级的, 如2+3*2
, 在计算时需要考虑到乘法的优先级大于加法, 但是转换为后缀表达式以后就没有优先级的问题了, 可以按照统一方式进行计算.
如何用后缀表达式求解
我们先不管中缀表达式是这么转化为后缀表达式的, 先来看一下假设我们已经转化为了后缀表达式, 那么如何用后缀表达式计算结果呢?
这里需要用到栈(stack)这种数据结构, 栈是一种先进后出的数据结构, 类似一个碗, 往里面放食物的时候是先装碗底, 再装碗中部, 最后装碗顶, 而吃的时候是反过来的, 先吃碗顶, 再吃碗中部, 最后吃碗底. 因此最先放进去的食物反而是最后被吃到的.
现在遍历后缀表达式4, 18, 9, 3, -, /, +
, 因为表达式中只有两种元素, 数字和操作符, 因此我们可以定义如下算法:
-
创建一个空栈stack.
-
遍历后缀表达式, 根据元素种类做如下操作: