有一类题目,需要我们与算术表达式打交道。而算术表达式根据运算符所在的位置可以分为三种表示方法:
- 前缀表达式(波兰式),如
(- (+ 3 (* 2 4)) 1)
,Lisp语言就是使用的这种表示方法 - 中缀表达式,如
3+2*4-1
,最适合人阅读的表示方法 - 后缀表达式(逆波兰式),如
3 2 4 * + 1 -
,计算机处理起来比较方便
计算后缀表达式很简单,只需要维护一个栈,让数字依次进栈,遇到运算符时,就弹出栈顶两个元素,将计算结果入栈,如图:
也可以用这种方法构建表达式树,但算法竞赛中一般不会有这样的需求。
为了把较难处理的中缀表达式转化为容易处理的后缀表达式,Dijsktra(没错又是他)引入了一个算法,称为调度场算法。
其实中缀表达式之所以难处理,本质上就是因为运算符优先级。如果我们把表达式全部按优先级加上括号,把3+5*4^2-1
变成(3+(5*(4^2)))-1
,很容易递归地计算。但是,“按优先级加括号”并不是很好实现,所以我们换一种思路,仍使用栈来解决问题。
为什么要使用栈?因为当我们读到a+b时,我们并不知道后面是a+b+c
还是a+b*c
,所以不能立刻转化为a b +
,而要暂缓一步,把加号存到栈里,等到有了足够的信息时,再把它放出来。
什么时候有足够的信息呢,不妨倒过来想。
假设乘方是表达式里优先级最高的运算,那我们一读到a^b
,不管b后面是什么运算符,都可以立刻把它变成a b ^
。
对于乘法,可能有a*b^c^d^...
这样的长链,但一旦我们读到另一个*
或者一个+
,形成了a*b^c^d^...*
或a*b^c^d^...+
的形式,前面那个*
便可以放心地放于此符号之前。
而加法也是类似,但是放出来的条件仅有读到另一个+
。
归纳一下发现,栈顶的运算符可以被弹出的条件是其优先级不低于新读入的运算符<