python_栈的应用:中缀表达式转换为后缀表达式

栈的应用:中缀表达式转换为后缀表达式

后缀表达式转换为中缀表达式

基本概念

  • 为了辅助算法的描述,引入两个概念
  • 算法执行的第一步是扫描表达式中的各个字符
  • 运算符:用来存放运算符的栈
    • 先扫描到的运算符先入栈
    • 操作数不设置栈(遇到操作数,直接输出)
    • 之后,我们用的来表示运算符栈标号
  • 将操作数记为operand
  • 将运算符(包括括号)记为ch,它包括
    • operator(+,-,*,/)
    • brackets("(",")")

手工转换:简单不含括号的情况算法过程的抽象

  • 从人工转为后缀表达式为中缀表达式的过程中,我们可以察觉到

    • 操作数之间的相对顺序在转换前后是不变的(在前面的操作数,转换后依然在前面)

    • 不同的是,括号消失了

    • 操作符的位置发生了改变而已

    • 并且乘法,除法,括号会改变表达式的计算顺序

      • 所以,当我们的算法扫描到运算符时到底对现有的栈做哪些变化(push/pop),需要做优先级比较
      • 而且由于栈的特点,我么只需要比较栈顶元素和刚刚扫描到的运算符就可以决定接着做的是出栈还是入栈操作
    • 这个过程可以理解为边读入分析,边输出确定的部分

      • 遇到操作数直接输出

      • 扫描到第一个运算符的时候,显然是要入栈的(后缀表达式总的就是运算符靠后输出,操作符的第二个操作数提前输出)

        • 其实,为了更加统一描述算法,可以将空栈描述为#,其优先级最为低(用0表示)
      • 扫描到下一个运算符时

        • 若,新运算符的优先级高

          • 那么要进行入栈

            • 高优先级运算符总是迫不及待想要出栈,但是为了防止后续扫描到更加高优先级的符号,他不得不等

            • 它必须等待下一个操作符,看它是否比自己具有更高优先级,如果没有,那就是他出栈的时机

            • 总之,还得再等下一个运算符出现

          • (这意味着,高优先级的运算符在出栈的时候是先出来的,能够率先和就近的操作数结合)

          • 例如

            • a+b-c:ab+c-
            • a+b*c:abc*+
        • 若新运算符的优先级低于(或者不高于)现有栈顶

          • 那么,让栈顶出栈(这个栈顶元素熬出头了),
            • 低优先级的运算符总是表现的谦让有加,让栈内的运算符先出栈(高优先级和同优先级的运算符)
            • 而后让该运算符号的进栈,(为了体现它的低优先级,迟缓它和操作数结合的时机)
            • 在具体就是让它入栈等待前,先让已存在的栈顶出栈完成结合,然后自己入栈
          • 例如
            • a*b+c:ab*c+

包含括号的情况分析

  • 为了使得上述算法稍加改造后能够处理带括号的情况,我们将括号也视为运算符
    • 将括号视为特殊的操作运算符,并未其分配合适的优先级
    • 而且左括号和右括号在不同的情况下(角色)具有不同的优先级
左括号
  • 作为待检测入栈时的左括号(icp)的优先级
    • 左括号无条件入栈(即便是连续多个左括号,它们都应该入栈)
  • 作为栈顶,此时它表现出来的优先级很低,以便能够接纳任何运算符ch
    • 仿佛开辟了一个全新的栈(内嵌于外层栈)
右括号
  • 入栈的时候右括号(icp)优先级很低(足以引出任意栈顶元素)
  • 作为栈顶元素时?
    • 一般来讲,右括号不会入栈的,(当右括号遇到时候左括号作为栈顶元素是,左右括号异同被丢弃,而且不作为输出(不打印出来))
    • 过程示例:(其实是一个现有符号栈的缩短过程(从遇到右括号开始,一直到这对括号消亡))
      • (+*-)
      • (+*)
      • (+)
      • ()
        • (此时所有内部运算符都被出栈,左括号也出栈,(扮演了若干次ch_now的右括号也被丢弃)
        • 随后,算法开始了对剩余输入符号串的读取和处理)

两套优先级标准

  • 引入两套标准不是算法必须的,但却是一个不错的idea

  • 为了更优雅的描述括号的优先级,我们在isp优先级标准的基础上,在创建一套优先级标准(icp)

  • 这使得我们可以更容易描述运算符出入栈的条件

    • 入栈条件:isp(top_ch)<icp(ch_now)
    • 出栈条件:isp(top_ch)>=icp(ch_now)
    • 括号对出栈:top_ch+ch_now=="()"
  • 为了更加同一的描述出入栈,进一步表示,(+-;*/)的优先级在两套标准中完全区分开来:

    • 入栈条件:isp(top_ch)<icp(ch_now)
    • 出栈条件:isp(top_ch)>icp(ch_now)
    • 括号对出栈:isp(top_ch)==icp(ch_now)
      • 比如:规定isp('(')==icp(')')==1
        • isp(')')==icp('(')==6
  • 其中括号的优先级分析是重点

    • 例如
      • a*(b+c):abc+*
      • (a+b)*c:ab+c*
    • 对于左括号,我们总是要让括号开辟一个新的环境
      • 这个左括号仿佛是嵌套的新栈的开端,等待右括号出现来封闭这个内嵌栈
        • 右括号连同它匹配的左括号(最近)引出栈
      • 凡是遇到左括号,无条件进栈!!
    • 对于右括号,它和左括号具有截然相反程度的优先级(右括号具有最低的优先级,它总是要引出栈内的运算符,而且通常右括号不会入栈!)
  • 栈总元素的变化取决于当前扫描到的运算符

    • (我们基于优先级规定了运算符栈元素的进出规则)

算法小结

  • 所有运算符在输出之前(和操作数发生作用之前),必须先进符号栈
    • 关于最后一个运算符号的出栈,可以通过设置一个虚拟运算终结符#(具有和括号一样的最低优先级),当转换函数扫描到#的时候,也可以确定所有运算符都已经出栈
  • 左括号的栈内优先级比任何实运算符都来的低,只有右括号的优先级比左括号低或者一样低
    • #,(,)是同一级别的低优先级特殊化运算符
  • 运算符在出栈前,必须好和后继者(刚扫描到的)运算符ch的优先级比较(从出栈者的角度)
    • 当它比后继运算符更高优先级,它才有机会出栈
  • 可见,左右括号内的运算符总是能够被最低优先级的右括号(icp)引出栈,最后连左括号也被引出,这时候再强制让右括号销毁,直到扫描到下一个运算符接替它

icp

  • in coming priority
  • 表示当前扫描到的运算符的优先级

isp

  • in stack priority

  • 表示栈内

代码示例

带有括号的情况

单套标准(isp)

s1 = "a+b-a*((c+d)/e-f)+g"
res1 = "ab+acd+e/f-*-g+"
s2 = "a+b-a*((c+d*a)/e-f)+g"
res2 = "ab+acda*+e/f-*-g+"
s3 = "(a+b)-a*((c+d-f))+g"
s_illegal_bracktes = "(c-f))+g"
sl = [s1, s2, s3,s_illegal_bracktes]
prior_d = {"+": 2, "-": 2}
lbs = ("(")
rbs = (")")
def isp_prior(ch):
p = 0
if ch in ["+", "-"]:
p = 2
elif ch in ["*", "/"]:
p = 3
elif ch in ["", "#"]:
p = 0
elif ch in rbs:
p = 1
elif ch in lbs:
# 左括号入栈的时候是无条件的
#但是要让左括号出栈,则只有右括号才可以,而遇到其他运算符是不出栈的!
#这是为了让括号的作用:提升其包裹的表达式内的计算优先级)
p = 1
return p
def get_top_ch(op_stack):
r = ""
if len(op_stack):
r = op_stack[-1]
return r
def putchar(s):
print(s, end='')
def pupo_next(op_stack, ch_now):
""" 这是一个递归函数
"""
top_ch = get_top_ch(op_stack)
if top_ch + ch_now in ["()", "[]", "\{\}"]: #出口1
op_stack.pop()
else: #ch_now 不是右括号
push_judger = (isp_prior(top_ch) < isp_prior(ch_now))
push_judger = push_judger or (ch_now in lbs) #ch_now是一个左括号
push_judger = push_judger or (top_ch in lbs) #ch_now是跟在左括号后面的运算符
if push_judger: #出口2
# 注意右括号和左括号相邻的robust
#入栈每次最多一个
op_stack.append(ch_now)
else:
# 出栈可能一口气出多个(这依赖于递归调用本函数)
if (len(op_stack)):
op = op_stack.pop()
if op not in lbs:
putchar(op)
top = get_top_ch(op_stack)
pupo_next(op_stack, ch_now)
def infix2postfix(s):
op_stack = []
for c in s:
if c.isalpha():
print(c, end='')
else:
# top_ch = get_top_ch(op_stack)
pupo_next(op_stack, c)
# if len(op_stack):
# putchar(op_stack[-1])
# print("".join(op_stack))
print(op_stack[0])
def is_legal_brackets(s):
""" 本函数利用栈检查一个表达式(中缀表达式)的括号是否是符合规范的(对于中缀转换后缀不是必须的) """
brackets_dict = {"(": ")", "[": "]", "{": "}"}
lbs = brackets_dict.keys() #左括号
rbs = brackets_dict.values() #有括号
# print(keys)
stack = []
i = 0
for c in s:
if c in lbs:
stack.append(c)
# continue
# if c not in lbs:
elif c in rbs:
if len(stack):
lb = stack.pop()
else:
return False
if c == brackets_dict[lb]:
# print(f"matched{i}:{lb}{c}")
i += 1
else:
# print("illegal brackets!!!")
return False
if len(stack) == 0:
# print("😊great!the string is legal brackets char sequence")
return True
else:
return False
# else:
# print("illegal brackets!!!")
# print(stack)
# if brackets_dict[c]:
if __name__ == "__main__":
# infix2postfix(s)
i=1
for s in sl:
if(not is_legal_brackets(s)):
print(i,":😡😠illegal infix expression")
break
print(i,":",s, "😁😀--> ", end='')
i+=1
infix2postfix(s)
posted @   xuchaoxin1375  阅读(17)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
历史上的今天:
2021-09-18 vim@修改@删除括号@引号中的内容删除光标所在单词
点击右上角即可分享
微信分享提示