中缀表达式和后缀表达式
平时用到的标准的四则运算表达式就叫做中缀表达式,例如“9 +(3 - 1) * 3 + 10 / 2)”,特点是运算符在数字中间;
后缀表达式就是一种把运算符放在数字后面的形式,“9 3 1 - 3 * + 10 2 / +”即为上例中缀表达式对应的后缀表达式形式,后缀表达式还有一个特点就是消除了所有的括号;
中缀表达式能够非常直观地展示出运算关系,很方便手动计算,但是如果要设计一个计算机程序来计算这个表达式却变得非常繁琐,不仅要考虑四则运算的优先级,还要考虑括号的影响,而后缀表达式虽然在表现形式上不直观却非常便于计算机进行计算。
后缀表达式计算结果
后缀表达式的计算要借助栈来实现。
规则:从左到右遍历表达式的每个数字和符号,遇到的是数字就进栈,遇到的时符号就将栈顶的两个数字出栈进行计算,然后将计算结果入栈,最终栈里的值即为计算的结果。
以中缀表达式“9 +(3 - 1) * 3 + 10 / 2)”的后缀表达式“9 3 1 - 3 * + 10 2 / +”为例,计算过程如下:
1、初始化一个空栈
2、后缀表达式前三个都是数字,所以9、3、1依次入栈,此时栈内元素为(最左边为栈底元素):9 3 1
3、接下来为减号“-”,此时1出栈作为第二个数,3出栈作为第一个数(因为栈内元素是先进后出,所以最先出栈的为运算中的第二个数,接着出栈的才是运算中的第一个数),进行进行减法运算3 - 1 = 2然后入栈,此时栈内元素为:9 2
4、接下来为数字3,进栈,栈内元素为:9 2 3
5、接下来为乘号“*”,此时3、2出栈,进行乘法运算2 * 3 = 6,结果入栈,此时栈内元素为:9 6
6、接下来是加号“+”,9、6出栈进行加法运算,结果15入栈,此时栈内元素为:15
7、接下来是数字10、2入栈,此时栈内元素为:15 10 2
8、接下来是除号“/”,10、2出栈进行除法运算,结果5入栈,此时栈内元素为:15 5
9、最后一个是加号“+”,15、5出栈进行加法运算,结果20即为整个表达式的运算结果,与中缀表达式计算结果一致
只需要按顺序遍历就能够计算出结果,不用考虑对四则运算法则做复杂的逻辑处理,对计算机处理来说是非常方便的,这样一来,最重要的问题就是怎样将中缀表达式转化为后缀表达式。
中缀表达式转化为后缀表达式
中缀表达式转化为后缀表达式同样要借助栈来实现,不同于中缀表达式的计算,这里的栈用于存贮运算符号而不是数值。
规则:从左到右遍历中缀表达式中的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号则要分为两种情况:
1)是括号时,如果是左括号,直接将左括号入栈,如果是右括号则栈顶元素依次出栈并输出,直到有一个左括号出栈(出栈的左括号不输出到后缀表达式)。
2)是运算符号时,如果栈顶符号为左括号,则直接将这个运算符号入栈。栈顶符号不为左括号时,如果该运算符号优先级比栈顶运算符号高则入栈,比栈顶符号低或者相等时则栈顶元素依次出栈并输出直到栈为空或者栈顶为左括号为止,然后将这个符号入栈。
最后将栈顶符号依次出栈并输出,得到的结果即为最终的后缀表达式。
同样以中缀表达式“9 +(3 - 1) * 3 + 10 / 2)”为例推导其后缀表达式的步骤如下:
1、初始化一个空栈
2、第一个数字9直接输出到后缀表达式,第二个符号“+”入栈,此时后缀表达式:9 栈内符号:+
3、接下里左括号入栈,数字3输出,此时后缀表达式为: 9 3 栈内元素为:+ (
4、接下来是减号“-”,由于栈顶为左括号,直接入栈,数字1输出,此时后缀表达式:9 3 1 栈内符号:+ ( -
5、接下来是右括号,栈顶符号“-”出栈并输出,接着栈顶左括号出栈,此时后缀表达式:9 3 1 - 栈内符号:+
6、接下来是乘号“*”,比栈顶符号加号“+”优先级高,入栈,数字3输出,此时后缀表达式:9 3 1 - 3 栈内符号:+ *
7、接下来是加号“+”,比栈顶“*”优先级低,栈顶“*”出栈并输出,接着与新的栈顶加号“+”比较优先级相同,栈顶“+”出栈并输出,栈为空,到此为止,然后将加号“+”入栈。此时后缀表达式为:9 3 1 - 3 * + 栈内符号:+
8、接下来数字10输出,除号“/”优先级比栈顶加号“+”优先级高直接入栈,此时后缀表达式:9 3 1 - 3 * + 10 栈内为:+ /
9、最后数字2输出,遍历结束后,栈顶符号依次出栈并输出,得到最终的后缀表达式:9 3 1 - 3 * + 10 2 / +
优化与整合
通过前面的介绍,只需要经过将中缀表达式转化为后缀表达式,再计算后缀表达式这两步就能得到一个四则运算表达式的值。这两步中各用到了一个栈,推导后缀表达式时用到的栈存储的是运算符号以及括号(只有左括号,没有右括号),计算后缀表达式时用到的栈存储的是数字,两个步骤分开先后执行要遍历一次中缀表达式载遍历一遍后缀表达式。同时在中缀表达式推导后缀表达式以及计算后缀表达式的过程中,都是要对字符串进行操作,如果将两步合并同时执行的话,不仅能够简化代码,更能提高运算效率,因为这样全程只需要遍历一次中缀表达式就可以完成计算。
思路:用一个栈data保存运算数字,一个栈opt保存运算符号。从左到右遍历中缀表达式,如果是数字就入栈data,如果是符号,以下四种情况直接将符号入栈opt:1)栈为空;2)栈顶为左括号;3)该符号为左括号;4)该运算符号优先级比栈顶符号高。如果是右括号,则执行一次计算步骤:从opt出栈一个运算符号,从data出栈两个数字进行一次运算并将结果入栈data。重复执行该计算步骤,直到opt栈顶为左括号,然后将该左括号出栈;如果该符号优先级低于opt栈顶符号或者与栈顶符号优先级相同时,重复执行与之前相同的计算步骤,直到opt栈为空,若中途opt栈顶符号为左括号则停止执行计算步骤。中缀表达式遍历完成后,继续执行之前的计算步骤直到opt栈为空。
因为Python处理字符串比较简洁方便,代码使用Python3编写,为输入方便起见,其中输入的表达式字符串中不包含空格,代码中忽略了对输入表达式不合法的处理,假设输入表达式是可以正确计算的:
def compare(op1, op2):
"""
比较两个运算符的优先级,乘除运算优先级比加减高
op1优先级比op2高返回True,否则返回False
"""
return op1 in ["*", "/"] and op2 in ["+", "-"]
def getvalue(num1, num2, operator):
"""
根据运算符号operator计算结果并返回
"""
if operator == "+":
return num1 + num2
elif operator == "-":
return num1 - num2
elif operator == "*":
return num1 * num2
else: # /
return num1 / num2
def process(data, opt):
"""
opt出栈一个运算符,data出栈两个数值,进行一次计算,并将结果入栈data
"""
operator = opt.pop()
num2 = data.pop()
num1 = data.pop()
data.append(getvalue(num1, num2, operator))
def calculate(s):
"""
计算字符串表达式的值,字符串中不包含空格
"""
data = [] # 数据栈
opt = [] # 操作符栈
i = 0 # 表达式遍历索引
while i < len(s):
if s[i].isdigit(): # 数字,入栈data
start = i # 数字字符开始位置
while i + 1 < len(s) and s[i + 1].isdigit():
i += 1
data.append(int(s[start: i + 1])) # i为最后一个数字字符的位置
elif s[i] == ")": # 右括号,opt出栈同时data出栈并计算,计算结果入栈data,直到opt出栈一个左括号
while opt[-1] != "(":
process(data, opt)
opt.pop() # 出栈"("
elif not opt or opt[-1] == "(": # 操作符栈为空,或者操作符栈顶为左括号,操作符直接入栈opt
opt.append(s[i])
elif s[i] == "(" or compare(s[i], opt[-1]): # 当前操作符为左括号或者比栈顶操作符优先级高,操作符直接入栈opt
opt.append(s[i])
else: # 优先级不比栈顶操作符高时,opt出栈同时data出栈并计算,计算结果如栈data
while opt and not compare(s[i], opt[-1]):
if opt[-1] == "(": # 若遇到左括号,停止计算
break
process(data, opt)
opt.append(s[i])
i += 1 # 遍历索引后移
while opt:
process(data, opt)
print(data.pop())
if __name__ == '__main__':
exp = "(9+((3-1)*3+10/2))*2"
calculate(exp)