【算法】中缀表达式转换成后缀表达式的算法理解

中缀表达式转换成后缀表达式的算法理解

【2012统考真题】已知操作符包括“+”、“-”、“*”、“/”、“(”和“)”。将中缀表达式a+b-a*((c+d)/e-f)+g转换为等价的后缀表达式ab+acd+e/f-*-g+时,用栈来存放暂时还不能确定运算次序的操作符。若栈初始时为空,则转换过程中同时保存在栈中的操作符的最大个数是

liuyubobobo 老师曾表达一个观点,学算法,光能够体会算法的精妙之处本来就是值得兴奋的事,若能将算法思想用于自己的工作中则更是难得的美事。

1. 概念

1.1 宏观概念

我们书写出一个含加减乘除等运算符的数学计算过程,我们能借助草稿纸得到答案。如以下的式子1 + 2 - 3 * ( (4 + 5) / 6 - 7 ) + 8,我们根据运算符的常识就能模拟整个运算过程。但是计算机要怎么读这一个字符串,并输出跟人类在草稿纸上计算一样的结果呢?
伟大的计算机科学家发明了通俗易懂的算法,该算法能分成两个子算法:
人类运算语言 -> 计算机整理后的运算数据计算机整理后的运算数据 -> 计算机运算
其中 人类运算语言 -> 计算机整理后的运算数据 == 中缀表达式 -> 后缀表达式
计算机先将人类的数学语言翻译成方便计算机运算的数据,表面上看,会将原有字符串中的括号删除(蕴含着匹配的逻辑)。这也能解释括号匹配的算法和表达式求值的算法能分开研究及学习,这本来就是可以独立出来理解的子过程。

1.2 直观印象

中缀表达式: 1 + 2
后缀表达式: 1 2 +

接下来参考王道书上3.6.11的习题解析,但采用自顶向下分析

2. 算法重要组成

2.1 数据结构

栈 (仅用一个)
操作符优先级定义表(人为规定)

2.2 行为列表

  • 操作符优先级比较 (查表)compare(栈外操作符, 栈内操作符) 值得注意的是,同样的操作符,栈内外的优先级不同, 这种查表的方式是很有意思的设计,后文将继续做分析
操作符#* /+ -)
栈内优先级(被考察后入栈的元素)01536
栈外优先级(当前正在考察的元素)06421
  • 入栈 push(栈外操作符)
  • 出栈 pop(栈内操作符)
  • 出栈并输出 popAndPrint(栈内操作符)
  • 考察下个元素 next()
  • 直接输出 print(当前正在考察的字符) (存在不入栈即可输出的当前考察对象)

2.3 算法边界

2.3.1 初始化

算法无条件得首先执行
push(#) 即初始状态时,栈内必有一个元素。
contact(inputString, #) 待考察的字符串后面加个 # 目的是:

  • 当字符串只有一个字符的时候compare(栈外操作符, 栈内操作符) 依旧合法。
  • compare(#, #) 可视为,所有字符串元素考察完成的信号
2.3.2 算法终点

当栈为空,字符串被已考察结束。程序可以结束。

3. 梳理算法执行过程

待考察字符串:a + b - a * ( (c + d) / e - f ) + g
上接 2.3 算法边界。代码块中符号[ 表示栈底

step1
String:  a + b - a * ( ( c + d ) / e - f ) + g #
current:# 当前考察元素为操作数为操作数,直接输出

print(a)
next()

 : [#
控制台 : a

step2
String:  a + b - a * ( ( c + d ) / e - f ) + g #
current:# compare(#, +) 当前待考察元素(也是栈外元素)比栈顶元素优先级高,入栈

push(+)
next()

 : [#+
控制台 : ab

step3
String:  a + b - a * ( ( c + d ) / e - f ) + g #
current:# 当前考察元素为操作数为操作数,直接输出

print(b)
next()

 : [#+
控制台 : ab

step4
4.1
String:  a + b - a * ( ( c + d ) / e - f ) + g #
current:# compare(+, -) 当前待考察元素比栈顶元素`+`优先级低

popAndprint(+)

 : [#
控制台 : ab+
4.2
String:  a + b - a * ( ( c + d ) / e - f ) + g #
current:# compare(#, -) 当前待考察元素比栈顶元素`#`优先级高,当前元素入栈

push(-)
next()

 : [#-
控制台 : ab+

step 2 和 step4 从行为上看不同,但是逻辑是统一的。描述为:任意两个操作符比较,至少输出一个优先级较大的操作符(查表),栈中保留的操作符(查表)优先级大小一定 小于等于 已经被输出的操作符。

思考两个问题:

  1. 栈中的元素如何保证最后一定输出完毕
    算法初始化时已经人为得在字符串后拼接了 “#”,也就是栈中存放任意多的元素,都会最后一次的compare(?, #) 操作将栈中元素依次 popAndPrint(next())
  2. 括号的数学符号意义是改变优先级,算法如何体现
    2.1 任意次遇到: 每次都立即入栈,继续遍历
    2.2 任意次遇到 ) :栈依次 popAndPrint(next()),直到遇到最近的一个,此时出现 compare('(', ')' ),两括号之间的表达式已经被正确处理。得益于栈内'('栈外')' 定义为优先级相等,是一个四则运算符之间比较不具有的结果特性。具体看到step12
step5
String:  a + b - a * ( ( c + d ) / e - f ) + g #
current:# 当前考察元素为操作数为操作数,直接输出

print(a)
next()

 : [#-
控制台 : ab+a
step6 ~ step11

根据上述描述可得

String:  a + b - a * ( ( c + d ) / e - f ) + g #
current:           ↑ ↑ ↑ ↑ ↑ ↑
 : [#-*((+
控制台 : ab+acd
step 12
String:  a + b - a * ( ( c + d ) / e - f ) + g #
current:
 : [#-*(
控制台 : ab+acd+

至此,第一组括号 () 已处理完毕,同理,当遇到下一个 ),效果类似,不再模拟。
整个过程结束后,即可回答前言的问题答案:同时保存在栈中的操作符的最大个数是 5 (已除去 #

4. 人为制定元素优先级的意义

如果元素变得可比较,Java可以用compareable接口实现,C++可用运算符重载,通用的方法是用Map<String , int> 存储,通过符号比较值大小即可。经过比较可以输出三种结果,即可赋予不同的语义。

  1. 当前考察元素 > 栈内元素
    当前操作符入栈
    现实场景:
    栈内 [#+ 考察 * => * 入栈。
    意义
    栈是后入先出,从数学角度,*的运算确保被优先执行。而执行的时机,见2

  2. 当前考察元素 < 栈内元素
    操作符出栈,直到当前考察元素 > 栈内元素 ,当前考察元素入栈。
    现实场景:
    2.1 栈内 [#+ 考察 - => + 出栈。
    2.2 栈内 [#+* 考察 - => * + 依次出栈
    2.3 栈内 [#+*(+ 考察 - => + 出栈 - 入栈

    意义
    1)栈内永远不会出现[#++[#+-[#-+[#-- 的情况,即从左到右遍历字符串,同等优先级的运算是从左往右依次输出。
    2)优先级高的运算符如* / 从左到右离最近的 + - 先被输出。如 1 + 2 * 3* 操作符会被先输出
    3)存在( 时,此时栈内运算符(的优先级最高,直到栈外出现),所有四则运算都会 “局部” 按照数学的优先级顺序进行输出。如 1 + 2 * ( 3 * 1 ), 3 * 1会被优先输出

  3. 当前考察元素 == 栈内元素
    由2.3知道,遍历过程中出现相等的情况,即“局部”所有符号已经处理完毕,现在需要做的就是,将栈内的(弹出即可。
    现实场景:
    3.1 栈内 [#+*( 考察 ) => ( 出栈
    3.2 栈内 [# 考察 # => # 出栈 => 栈为空 => 程序结束

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值