中缀即我们平时用的数学表达式,其递归定义为中缀表达式 运算符 中缀表达式。举例:1+2,(1+2)*(3+4).这种表达式便于直观理解但是不方便计算机计算。后来有波兰人发明了前缀,后缀表达式。前缀表达式也叫波兰式,后缀表达式也叫逆波兰式。前后缀表达式经常作为栈在优先级的应用的例题。
前缀表达式递归定义为 运算符 前缀表达式 前缀表达式。
前缀计算方式即将运算符最右边两个放一起计算。
例题1:中缀表达式:1+((2+3)*4)-5,其对应前缀为- + 1 * + 2 3 4 5.前缀求值方式为每次取两个数字进行计算。具体如下(声明两个stack, s1和s2)
s1 | s2 | 说明 |
---|---|---|
- + 1 * + 2 3 4 5 | 初始 | |
- + 1 * + 2 3 4 | 5 | |
- + 1 * + 2 3 | 4 5 | |
- + 1 * + 2 | 3 4 5 | |
- + 1 * + | 2 3 4 5 | |
- + 1 * | 5 4 5 | 2 3出栈,做加法再push回去 |
- + 1 | 20 5 | 5 4出栈,做乘法再push回去 |
- + | 1 20 5 | |
- | 21 5 | 20 1出栈,做加法再push回去 |
16 | 21 5出栈,做加法再push回去 |
最后结果16.
可见前缀表达式通过栈保证每次都是当前优先级最高的两个做运算,也可以理解为保证先计算当前子前缀表达式。py代码如下:
def calPre(number):
stack = []
while number:
if number[-1].isdigit():
stack.append(int(number[-1]))
number.pop()
else:
a = stack[-1]
b = stack[-2]
stack.pop()
stack.pop()
if number[-1] == '+':
stack.append(a+b)
elif number[-1] == '-':
stack.append(a-b)
elif number[-1] == '*':
stack.append(a*b)
else:
stack.append(a/b)
number.pop()
return stack[0]
那么先问问题来了,怎样将中缀变前缀?流程如下(摘抄自https://blog.csdn.net/idestina/article/details/81705516):
1.初始化两个栈:运算符栈S1和储存中间结果的栈S2;
2.从右至左扫描中缀表达式;
3.遇到操作数时,将其压入S2;
4.遇到运算符时,比较其与S1栈顶运算符的优先级:
4.1. 如果S1为空,或栈顶运算符为右括号“)”,则直接将此运算符入栈;(右括号出现为了等待左括号,另外如果栈顶是右括号,此时开始计算子缀表达式)
4.2. 否则,若优先级比栈顶运算符的较高或相等,也将运算符压入S1;
4.3. 否则,将S1栈顶的运算符弹出并压入到S2中,再次转到(4-1)与S1中新的栈顶运算符相比较;(只有当 当前运算符级别比栈顶的低时才把栈顶的弹出,这样保证低级别尽可能留在最后计算,这句话纯属个人理解)
5.遇到括号时:
5.1. 如果是右括号“)”,则直接压入S1;
5.2.如果是左括号“(”,则依次弹出S1栈顶的运算符,并压入S2,直到遇到右括号为止,此时将这一对括号丢弃;(一对括号内部是一个子表达式,也是一个子前缀表达式,虽然物理上是一串字符串,但是逻辑上就是一个数字因此需要将其算完)
6.重复步骤(2)至(5),直到表达式的最左边;
7.将S1中剩余的运算符依次弹出并压入S2;
8.依次弹出S2中的元素并输出,结果即为中缀表达式对应的前缀表达式。
这里边最关键的是栈的应用,如何通过栈安排好不同优先级的运算符。
例题2:已知中缀表达式:1+((2+3)*4)-5 求前缀表达式。
当前符号 | s1(符号栈) | s2(暂存结果) | 说明 |
---|---|---|---|
5 | 5 | ||
- | - | 5 | 符号栈空,直接push进去 |
) | )- | 5 | )直接push进去 |
4 | )- | 4 5 | )直接push进去 |
* | *)- | 4 5 | 栈顶是右括号,*直接push进去 |
) | )*)- | 4 5 | )直接push进去 |
3 | )*)- | 3 4 5 | 3直接push进去 |
+ | +)*)- | 3 4 5 | +直接push进去 |
2 | +)*)- | 2 3 4 5 | 2 直接push进去 |
( | *)- | + 2 3 4 5 | 遇到(,就把符号栈依次出栈直到遇到),)丢弃 |
( | - | * + 2 3 4 5 | 再次遇到(,就把符号栈依次出栈直到遇到),)丢弃 |
+ | ± | * + 2 3 4 5 | 栈顶优先级不低于+ 直接入栈 |
1 | ± | 1* + 2 3 4 5 | 1入栈 |
-+1* + 2 3 4 5 | 剩余依次入栈 |
另外如果仅仅为了手工生成前缀,可以这样做:1+((2+3)*4)-5
还是从右往左,遇到第一个符号是-,可以把中缀表达式分成两个部分:
1+((2+3)*4)-5,那么其前缀表达式为 :
-①1+((2+3)*4)②5
下一步是把①②分别转化成中缀表达式,②不用转,直接看①
①可以写成:
+③1 ④((2+3)*4))
之后每一步如法炮制……
下面是算法的代码实现:
level = {'+':0,'-':0,'*':1,'/':1,'(':2,')':2}
def midToPre(str):
symbol,number = [],[]
for i in range(len(str)-1,-1,-1):
s = str[i]
if s.isdigit():
number.append(s)
else:
if not symbol or s == ')' or symbol[-1] == ')':#左括号或者symbol空直接入栈
symbol.append(s)
elif s == '(':
while symbol[-1] != ')':
number.append(symbol[-1])
symbol.pop()
symbol.pop()
else:
while symbol and level[s] < level[symbol[-1]]:
number.append(symbol[-1])
symbol.pop()
symbol.append(s)
while symbol:
number.append(symbol[-1])
symbol.pop()
number.reverse()
return number
前缀变中缀的过程类似前缀求值计算。每次弹出两个,当当前运算符高于上次运算符时加上括号。
例题:-+1* + 2 3 4 5 变中缀
当前符号 | stack | 说明 |
---|---|---|
5 | 5 | |
4 | 4 5 | |
3 | 3 4 5 | |
2 | 2 3 4 5 | |
+ | 2+3 4 5 | |
* | (2+3)*4 5 | *高于+,加上括号 |
1 | 1 (2+3)*4 5 | |
+ | 1+(2+3)*4 5 | |
- | 1+(2+3)*4 - 5 |
代码如下:
def preToMid(preList):
stack = []
last = ''
for i in range(len(preList)-1,-1,-1):
s = preList[i]
if s.isdigit():
stack.append(s)
else:
if last != '' and level[s] > level[last]:
stack[-1] = ')'+stack[-1]+'('#最后要翻转
a = stack[-1]
b = stack[-2]
stack.pop()
stack.pop()
stack.append(b+s+a)
last = s
return stack[0][::-1]
后缀表达式顾名思义,运算符在 后边,定义为 后缀表达式 运算符 后缀表达式。例如:1+((2+3)×4)-5的后缀表达式为1 2 3 + 4 × + 5 -。这个求值和前缀类似 ,不再赘述。
1.初始化两个栈:运算符栈S1和储存中间结果的栈S2;
2.从左至右扫描中缀表达式;
3.遇到操作数时,将其压入S2;
4.遇到运算符时,比较其与S1栈顶运算符的优先级:
4.1. 如果S1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
4.2. 否则,若优先级比栈顶运算符的较高(不含 相等),也将运算符压入S1;
4.3. 否则,将S1栈顶的运算符弹出并压入到S2中,再次转到(4-1)与S1中新的栈顶运算符相比较;(只有当 当前运算符级别比栈顶的低时才把栈顶的弹出,这样保证低级别尽可能留在最后计算,这句话纯属个人理解)
5.遇到括号时:
5.1. 如果是左括号“(”,则直接压入S1;
5.2.如果是右括号“)”,则依次弹出S1栈顶的运算符,并压入S2,直到遇到右括号为止,此时将这一对括号丢弃;
6.重复步骤(2)至(5),直到表达式的最左边;
7.将S1中剩余的运算符依次弹出并压入S2;
8.依次弹出S2中的元素并输出,结果即为中缀表达式对应的前缀表达式。
注意与前缀的区别。
略