逆波兰表达式│算法与数据结构

Posted python杂货铺

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了逆波兰表达式│算法与数据结构相关的知识,希望对你有一定的参考价值。

零: 提出问题

假如需要你将两个已知的数字相加或者相乘,用代码表达出来是不是非常的 easy。再假如给出的是类似 1+1由一个符号两个数字组成的字符串,要求出它的结果,可以用 split() 函数分割字符串后进行计算,也是没有多少难度。

那就再升级一步,如果这个字符串不止有两个数字和一个符号,是一个包含加减乘除和括号的复杂算术表达式呢?比如下面的一个算术表达式:

1 + ( 2 - 3 * 4 ) / 5 + 6

我们可以使用  来完成这个算术表达式的计算。创建两个栈分别存放数字和符号,然后通过符号间的优先级关系判断是否前一个符号是否可以计算。具体方法在文章  已经进行了详细的解析,本文就不再重复解析这种方法了,想要了解的小伙伴可以点击链接查看文章。

现在就请出本文的主角!

壹: 主角出现

本文的主角叫 逆波兰表达式,也叫后缀表达式。后者这个比较通俗的名字很明确的表示了自己的含义,意思是计算的符号在要计算的两个数字之后。

我们常用的算术表达式是中缀表达式,计算符号在两个数字之间。在现实中基本没有直接给出一个逆波兰表达式的场景,也就意味着必须先将中缀表达式转换成逆波兰表达式。

我们使用上面的例子,可以先看看转换成逆波兰表达式是什么样子?

1 2 3 4 * - 5 / + 6 +

贰: 这是怎么做到的

好奇怪的一串字符,它是怎么转化来的呢?它真的能用来计算吗?我们先来解析一下第一个问题:

中缀表达式:

1 + ( 2 - 3 * 4 ) / 5 + 6

第一步:
所有算术步骤加上括号,表明优先级
这一步去除了符号与符号间的优先级关系, 计算先后顺序依照括号进行. :乘除要优先于加减

( ( 1 + ( ( 2 - ( 3 * 4 ) ) / 5 ) ) + 6 )

第二步:
将括号内的符号移动到被包裹的最内层的右括号之后

( ( 1 ( ( 2 ( 3 4 ) * ) - 5 ) / ) + 6 ) +

第三步:
去掉括号

1 2 3 4 * - 5 / + 6 +

看到现在,是不是还是一头雾水,所以,一定要坚持往下看! 进行下一步骤。

现在肯定要讲转换到的逆波兰表达式怎么计算了,否则各位读者老爷都不耐烦的走光了。

从逆波兰表达式的左侧开始,先找到第一个符号,抽出符号前的两个数字并计算。

1. 找到符号 `*`, 进行计算 `3 * 4 = 12`, 当前表达式为 `1 2 12 - 5 / + 6 +`
2. 找到符号 `-`, 进行计算 `2 - 12 = -10`, 当前表达式为 `1 -10 5 / + 6 +`
3. 找到符号 `/`, 进行计算 `-10 / 5 = -2`, 当前表达式为 `1 -2 + 6 +`
4. 找到符号 `+`, 进行计算 `1 + -2 = -1`, 当前表达式为 `-1 6 +`
5. 找到符号 `+`, 进行计算 `-1 + 6 = 5`, 得到结果为 `5`

看完示例,是不是豁然开朗了呢,将表达式转换成逆波兰表达式,可以从左到右,检索到一个符号就立刻进行计算。完全去除了括号的强制优先运算和运算符之间的优先级关系。

叁: 我们需要做一道题

leetcode 150 题目就是关于逆波兰表达式求值的:

150. 逆波兰表达式求值

根据 逆波兰表示法,求表达式的值。

有效的运算符包括 +, -, *, /
每个运算对象可以是整数,也可以是另一个逆波兰表达式。

说明:

- 整数除法只保留整数部分。
- 给定逆波兰表达式总是有效的。
换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

示例 1

输入: ["2", "1", "+", "3", "*"]
输出: 9
解释: 该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

想要实现逆波兰表达式的运算,还是需要引入 -- 栈。将数值元素依次加入栈中,如果遇到符号,则将栈中的最后两个数值出栈,注意先后顺序,先出栈的在计算时处于符号之后,后出栈的数值在计算时处于符号之前。计算完成后将得到的数值入栈。最后,数值栈中仅保留一个数值元素,这个就是计算结果。

class Solution:
def evalRPN(self, tokens: list[str]) -> int:
数值栈 = []
for token in tokens:
if token not in "+-*/": # 当前元素是数字
数值栈.append(int(token))
else:
数值后 = 数值栈.pop()
数值前 = 数值栈.pop()
if token == '+':
数值栈.append(数值前 + 数值后)
elif token == '-':
数值栈.append(数值前 - 数值后)
elif token == '*':
数值栈.append(数值前 * 数值后)
elif token == '/':
数值栈.append(int(数值前 / 数值后))
return 数值栈.pop()

肆: 上一道题好像考察不全面

上题直接给出了逆波兰表达式,我们只需要求解即可。那如果只给出一个普通的表达式,又需要使用逆波兰表达式进行计算。那使用代码如何去做呢?

def 中缀表达式转逆波兰表达式(s):
'''假设 s 是一个仅有数字和运算符号(包含括号)的有效中缀表达式'''
符号栈, 表达式队列, 上一字符 = [], [], ''
# 符号优先级(栈中最后一位与当前符号进行比较)
符号优先级表 = {
"+": {"+": '>', '-': '>', '*': '<', '/': '<', '(': '<', ')': '>'},
"-": {"+": '>', '-': '>', '*': '<', '/': '<', '(': '<', ')': '>'},
"*": {"+": '>', '-': '>', '*': '>', '/': '>', '(': '<', ')': '>'},
"/": {"+": '>', '-': '>', '*': '>', '/': '>', '(': '<', ')': '>'},
"(": {"+": '<', '-': '<', '*': '', '/': '', '(': '<', ')': '='},
")": {"+": ' ', '-': ' ', '*': '', '/': '', '(': ' ', ')': ' '},
}
for value in s:
if value not in '+-*/()': # 是数字
if 上一字符 not in '+-*/()': # 是连续的数字
表达式队列.append(表达式队列.pop() * 10 + int(value))
else:
表达式队列.append(int(value))
else:
while True: # 在栈中可能有多个优先级更高的符号, 需要遍历
if len(符号栈) == 0 or 符号优先级表[符号栈[-1]][value] == '<': # 当前符号优先级更高, 加入栈
符号栈.append(value)
break
elif 符号优先级表[符号栈[-1]][value] == '=': # 结束符遇到了结束符
符号栈.pop()
break
elif 符号优先级表[符号栈[-1]][value] == '>': # 栈中优先级更高, 可计算
表达式队列.append(符号栈.pop())
上一字符 = value
for 符号 in 符号栈: # 符号栈中可能会剩余一个符号, 直接加入
表达式队列.append(符号栈.pop())
return 表达式队列


if __name__ == "__main__":
print(中缀表达式转逆波兰表达式('1+(2-3*4)/5+6'))

以上代码执行后得到的结果是:

[1, 2, 3, 4, '*', '-', 5, '/', '+', 6, '+']

是不是转换成了和 leetcode 150 题一样的表达式了? 那么接下来的步骤就不用多言了吧。

伍: 宣传一下自己


以上是关于逆波兰表达式│算法与数据结构的主要内容,如果未能解决你的问题,请参考以下文章

逆波兰表达式│算法与数据结构

逆波兰表达式│算法与数据结构

数据结构与算法之深入解析“逆波兰表达式求值”的求解思路与算法示例

数据结构与算法-使用栈《逆波兰表达式求值问题》

北京大学 程序设计与算法逆波兰表达式

重新整理数据结构与算法——逆波兰表达计算器[八]