众所周知啦,我们数学里面的公式就是中缀表达式(infix),形如a*(b+c),支持括号用于调整运算的顺序。我们平常用的就是中缀表达式。
那么什么是后缀表达式(postfix)?
后缀表达式(又称为逆波兰reverse polish)就是不需要括号就可以实现调整运算顺序的一种技法。
比如:ab+cde+**
上面就是一个典型的后缀表达式,将它改为中缀表达式其实是(a+b)*((d+e)*c)。我们这样将后缀表达式转换成中缀表达式,虽然符合我们的数学计算习惯,但是并不符合计算机运算的方式。我们可以通过数据结构中的栈来理解后缀表达式和它为什么符合计算机计算。
什么是栈(stack)呢?简单的说就是LIFO(后入先出)。画一张图就是下面这样
堆栈的操作其实很简单,你可以把堆栈想象成一个汉诺塔,你可以在塔顶不断加上汉诺圈,可以从顶部一个一个拿走,但是你不能直接从下面拿走最先放的那个圈。所以你懂了吧,要想拿到最先那个,你必须取出所有的圈,而你最后摆上去的圈可以被立刻拿到。这就是LIFO的本质。
堆栈的实现方法可以是链表或者数组。它们各有各得优点,它们的对比网络上有很多啦,当然我以后也会写出来。
现在你知道了栈这种结构就可以重新理解一遍后缀表达式。我们就以 "ab+cde+**" 这个表达式举例。我们创建一个栈,然后从这个表达式开头(当然是从左边)扫描,如果扫描到的是数据就压(push)到栈中。那么在扫描两个字符后栈里情况是这样:
接着我们发现了一个操作符(operator),是一个加号(+)。对于每个操作符我们进行如下操作:
1. 从栈中取出两个操作数,比如这里的a,b
2. 对这两个操作数进行操作符的运算,如这里的a+b
3. 将操作结果压到栈中,如假设这里的结果m(m=a+b),将m压到栈中
此时栈中的情况是这样的:
重复上述的过程,你可以发现后缀表达式实际上计算了中缀表达式的运算式。
我们会发现当我们扫描到整个后缀表达式的结尾的时候,我们正好计算完整个表达式,因此对于拥有N个字符的后缀表达式,我们计算它的时间是O(N),即为线性时间。
好了,后缀表达式的运算就是这样的了,现在我们来说一下中缀表达式的运算。
我们依旧使用(a+b)*(c*(d+e))来举例。对于中缀表达式我们这里采用转换为后缀表达式的方法,依旧使用堆栈来实现计算。
先介绍一下手写时转换的方法
对于((a+b)*(c*(d+e)))将其中的操作符提到对应的括号的后面,得到像这样的中间转换式
((ab)-(c(de)+)*)*
然后我们把这个中间转换式去掉括号,得到后缀表达式
ab-cde+**
现在我们来实现计算机利用堆栈的转换方式,转换规则如下:
1. 我们规定+-的优先级为1,*/的优先级为2,( 左括号的优先级为9,) 右括号的优先级为0。
2. 我们规定只有高优先级的运算符可以压栈,低优先级或者同优先级运算符直接输出。
3. 我们规定( 压栈后,下一个运算符(右括号除外)均可以压栈。
4. 我们规定遇到) 后,一直弹出栈内的操作符直到对应的( 出现。
5. 我们规定操作数均输出,当扫描完成弹出栈中所有元素。
我们使用图例来直观展示一下:
上面就是中缀表达式转为后缀表达式的方法了。我们发现转换的时间复杂度也是O(N)。
接下来我给大家介绍一下一个神奇的数据结构,他可以通过不同遍历方式组合成中辍表达式和后缀表达式。他就是——二叉树!
二叉树定义:每个节点最多有两个子树的树结构。关于它的知识实在太多,我在这里不一一介绍,大家可以从网上搜集相关资料。
先给大家看一下二叉树表示我们举例的那个算式:
我们先尝试一下中序遍历,什么是中序遍历?你可以简单的认为是先左,再根,最后右的遍历顺序。注意树结构的思维方式是递归,因此我们从最左边的a开始遍历,遍历顺序依次为a——》+——》b——》*——》c——》*——》d——》+——》e。在遍历的时候你可以认为某一部分是一个整体,这样严格遵循left-parent-right的方式就可以得到中缀表达式了。
那么最后当然是符合计算机计算的后缀表达式,我们使用后序遍历(先左再右最后根)。遍历顺序是a——》b——》+——》c——》d——》e——》+——》*——》*
ps:其实通过前序遍历,我们可以得到前缀表达式,不过这玩意真没多大用,爱钻研的各位可以自己搜一下。
好的,我的思考就到这里了……百尺竿头,今进一步!
来源:https://www.cnblogs.com/SmartRabbit/p/4416032.html