【前情提要】
之前写了一篇中缀转后缀表达式的文章👉中缀表达式转后缀表达式 + 后缀表达式的计算,最近回顾了一下,发现内容还比较浅显,只是给出了转换的方法,按照固定的流程进行就好,但是对于背后的原理并没有具体解释。刚开始学习时我也只是一股脑将转换的规则背下来,虽然应付考试没什么问题,但考完后很快就忘记了,复习时又是一片茫然。所以决定花点时间理清转换的原理以及自己的一些疑问,希望能更好地理解转换的过程,而不是纯靠死记硬背。
一、中缀表达式和后缀表达式的区别
中缀表达式:操作符位于两个运算数中间(如 3 + 2)。计算时要综合考虑操作符的优先级和括号。
如 5*(2+1) ,虽然 * 的优先级高于 + ,但括号的存在表示应优先执行括号内的 + 运算。
后缀表达式:操作符位于两个运算数后面(如 3 2 +)。计算时无需考虑操作符的优先级,也无需考虑括号,只需根据操作符在表达式中从左到右出现的顺序依次进行运算。
如 2 3 + 5 4 - *,虽然 * 的优先级高于 + 和 - ,但按照后缀表达式的计算规则,从左往右看 + 最先出现,所以 + 先进行运算,然后是 - ,最后是 * 。
总结:后缀表达式严格按照从左到右、每个操作符出现的先后进行运算,所以后缀表达式最重要的就是确定操作符在表达式中的顺序。
二、中缀转后缀 - 转换规则分析
1、从左至右依次遍历中缀表达式,并准备一个栈存储操作符
Q:这个栈到底用来干什么?
A:此栈用于存放暂时无法确定运算顺序的操作符——入栈的操作符说明还不能确定运算顺序,出栈的操作符就说明已经确定运算顺序,可直接输出到后缀表达式。
因为中缀表达式的计算要综合考虑操作符的优先级以及括号,故从左至右遍历中缀表达式,先出现的操作符不一定能够先执行运算,而是要根据后面出现的操作符进行判断。
【举个栗子】
现有一个中缀表达式 2 + 3 * 5 ,从左向右遍历,虽然 + 最先出现,但明显 + 不能先运算,因为后面出现的 * 优先级高于 + ,所以 * 应该先运算,然后才是 + 。
2、运算数 :直接输出
Q:为什么运算数可以直接输出,中缀和后缀表达式中运算数的相对顺序相同吗?
A:中缀和后缀表达式中所有运算数的相对顺序是相同的。
因为中缀表达式和后缀表达式的本质区别在于操作符究竟位于两个运算数中间还是后面,而两个运算数的相对位置并没有变化,故可以直接输出到后缀表达式。
【举个栗子】
现有一个中缀表达式 2*(3+5)+7/1-4 ,转换为后缀表达式 2 3 5 + * 7 1 / + 4 - ,其中六个运算数 2 3 5 7 1 4 在两个表达式中的相对顺序的是相同的。
3、操作符 :入栈(入栈前需判断是否要先出栈)
① 栈空 or 栈顶操作符 < 该操作符(优先级):直接入栈
② 栈顶操作符 ≥ 该操作符(优先级):持续出栈,并将出栈操作符依次输出到后缀表达式,直到栈空 or 栈顶操作符 < 该操作符,该操作符再入栈。
Q:为什么操作符必须入栈?且入栈前还要根据优先级进行出栈操作?
A:入栈是因为暂时无法确定运算顺序,入栈前出栈则是确定了前面的操作符的运算顺序。
已知这个栈是用于存放暂时还无法确定运算顺序的操作符,且中缀表达式的运算顺序并不是按照操作符从左到右出现的顺序执行的。一个操作符能否直接运算,需要和后面的操作符进行优先级比较才能确定,所以每扫描到一个操作符,都要入栈一次,同时入栈的操作符也为前面还未能确定运算顺序的操作符提供了参考。
【举个栗子】
现有一个中缀表达式 2 * 3 + 5 ……,将式子遮住,从左边开始一点点露出,模拟从左往右遍历的过程。
① 从左向右遍历,第一个扫描到的操作符为 *
但现在还无法确定 * 的运算顺序,因为表达式后面的情况未知 2 * ???,比如:
如果接下来是 2 * 3 + 5 ,那么 * 就应该先于 + 运算;
如果接下来是 2 * (3 + 5) ,那么 + 就应该先于 * 运算;
所以 * 的运算顺序还无法确定,只能先入栈。
② 继续往后遍历,第二个扫描到的操作符为 +
同样的,现在也无法确定 + 的运算顺序,因为后面的情况未知 2 * 3 + ???,比如:
如果接下来是 2 * 3 + 5 - 2 ,那么 + 就应该先于 - 运算(左优先原则);
如果接下来是 2 * 3 + 5 / 2 ,那么 / 就应该先于 + 运算;
所以 + 的运算顺序也无法确定,也要入栈。但是,+ 却为前面还未确定顺序的 * 提供了参考:优先级 * > + ,说明前面的 * 先于 + 运算,故 * 的运算顺序确定,即:* 出栈并输出到后缀表达式,+ 再入栈。
总结:从左往右遍历中缀表达式,扫描到的每个操作符在其后的操作符还未知时,都无法确定运算顺序,故需要入栈。同时,每个入栈的操作符也为前面的操作符的运算顺序提供了参考。
4、左括号 :直接入栈(入栈后优先级降至最低)
5、右括号 :直接出栈,并依次输出到后缀表达式,直到左括号出栈
6、重复以上步骤直至中缀表达式遍历完成,且操作符全部出栈
这三条规则的原理和前面也大同小异,出栈和入栈的目的都是为了确定操作符的运算顺序。