逆波兰表示法:求值数学表达式的优雅替代方案
逆波兰表示法 (RPN) 是一种高效的数学表达式求值方法,其特点是将运算符置于运算数之后。这种方法允许省略括号,简化并清晰化计算过程。尽管乍一看可能有些不同,但使用 RPN 可以显著加快运算的执行速度,尤其是在计算机系统和可编程计算器中。
 
                    编写数学表达式的方法有很多种。最常见的是中缀表示法,其中运算符位于运算数之间,例如 A + B 或 (C  D) - E。虽然这种表示法对于我们人类来说似乎很直观,但它不一定是计算机和计算器的最佳解决方案。在本文中,我们将探讨一种不太为人所知,但可以说更优雅的替代方案:逆波兰表示法 (RPN),也称为后缀表示法。
什么是逆波兰表示法?
RPN 是一种数学表示法,其中运算符位于其运算数之后。在 RPN 中,我们不写 A + B,而是将其表示为 A B +。乍一看可能有些不寻常,但它的优点很快就会显现出来。
RPN 的概念起源于波兰数学家扬·武卡谢维奇,他在 20 世纪 20 年代开发了前缀表示法(波兰表示法),其中运算符位于其运算数之前。后缀表示法是前缀表示法的一种变体,由澳大利亚计算机科学家查尔斯·汉布林在 20 世纪 50 年代进一步发展,以简化计算机处理。
为什么 RPN 有用?
RPN 的主要优点是消除歧义和计算效率。中缀表示法需要括号来明确运算顺序,而计算机需要复杂的解析算法才能正确处理运算符优先级。然而,在 RPN 中,括号是不必要的,运算顺序由表达式的结构自动确定。
这对于早期硬件资源有限的计算机和袖珍计算器尤为重要。使用 RPN 的计算器可以更简单、更快速地执行计算。
历史花絮: RPN 表示法最初由惠普 (HP) 公司在 20 世纪 60 年代和 70 年代推广开来。HP-9100A 台式计算器 (1968) 是首批使用 RPN 的商用计算器之一。后来,传奇的 HP-35 (1972)——第一款科学袖珍计算器,也采用了 RPN,这种方法长期以来一直是惠普专业计算器的标志。RPN 计算器以其快速准确的计算而闻名,并在工程师、科学家和金融专业人士中广受欢迎。
RPN 的工作原理
让我们看一个 RPN 工作原理的示例。考虑中缀表达式:(3 + 4)  2 - 5。
首先,我们将其转换为 RPN。对于转换,我们必须考虑运算顺序。在本例中,我们首先执行括号内的加法,然后乘以 2,最后减去 5。RPN 表达式将是:3 4 + 2  5 -。
现在,让我们看看如何使用堆栈来求值这个 RPN 表达式。堆栈是一种数据结构,可以将元素放入(入栈)和移除(出栈),始终先取出最后插入的元素(LIFO - 后进先出)。
RPN 表达式的逐步求值:
- 3: 读取- 3。这是一个运算数,将其压入堆栈。堆栈状态:- [3]
- 4: 读取- 4。也是一个运算数,将其压入堆栈。堆栈状态:- [3, 4]
- +: 读取- +运算符。从堆栈中弹出两个运算数(先弹出- 4,然后弹出- 3)。执行加法:- 3 + 4 = 7。将结果 (- 7) 压回堆栈。堆栈状态:- [7]
- 2: 读取- 2。运算数,压入堆栈。堆栈状态:- [7, 2]
- : 读取- 运算符。从堆栈中弹出两个运算数(先弹出- 2,然后弹出- 7)。执行乘法:- 7 2 = 14。将结果 (- 14) 压回堆栈。堆栈状态:- [14]
- 5: 读取- 5。运算数,压入堆栈。堆栈状态:- [14, 5]
- -: 读取- -运算符。从堆栈中弹出两个运算数(先弹出- 5,然后弹出- 14)。执行减法:- 14 - 5 = 9。将结果 (- 9) 压回堆栈。堆栈状态:- [9]
由于我们已经到达 RPN 表达式的末尾,并且堆栈上只剩下一个元素,因此该元素是最终结果。因此,以 RPN 求值的表达式 (3 + 4)  2 - 5 的值为 9。
如何将中缀表达式转换为 RPN?
将中缀表达式转换为 RPN 最常见的算法是调度场算法,由埃德斯加·戴克斯特拉开发。该算法使用堆栈(用于运算符和括号)和输出队列(在实践中可以只是一个列表)进行转换。
调度场算法的步骤:
- 词法分析: 将中缀表达式分解为词法单元(数字、运算符、括号)。
- 初始化: 创建一个空堆栈用于存储运算符,并创建一个空输出队列用于构建 RPN 表达式。
- 处理词法单元: 遍历词法单元: - 数字: 如果词法单元是数字,则将其添加到输出队列。
- 运算符 (op1): 如果词法单元是运算符: - 当堆栈顶部存在运算符 (op2),并且满足以下任一条件时: - op2 的优先级高于 op1,或者
- op2 的优先级等于 op1 且 op1 是左结合的(在我们的示例中,所有运算符都是左结合的),
 
- 从堆栈中弹出 op2 并将其添加到输出队列。
- 将 op1 压入堆栈。
 
- 当堆栈顶部存在运算符 (op2),并且满足以下任一条件时: 
- 左括号 (: 将其压入堆栈。
- 右括号 ):- 当堆栈顶部的运算符不是左括号时: - 从堆栈中弹出运算符并将其添加到输出队列。(如果在找到左括号之前堆栈变为空,则存在不匹配。)
 
- 如果堆栈顶部存在左括号,则从堆栈中弹出它(但不要将其添加到输出队列——括号不包含在 RPN 表达式中)。
- 如果在处理右括号后,堆栈顶部不是左括号,则括号不匹配。
 
- 当堆栈顶部的运算符不是左括号时: 
 
- 清空堆栈: 当没有更多词法单元要读取时,从堆栈中弹出任何剩余的运算符并将其添加到输出队列。
- 结果: 输出队列现在包含 RPN 表达式。
优先级和结合性:
理解运算符优先级和结合性非常重要。通常的优先级顺序(从高到低):
- 乘法 (),除法 (/)
- 加法 (+),减法 (-)
所有四个基本算术运算都是左结合的,这意味着具有相同优先级的运算符从左到右求值(例如,a - b - c = (a - b) - c)。
使用调度场算法将中缀表达式 (3 + 4)  2 - 5 转换为 RPN 的示例:
| 词法单元 | 输出队列 | 运算符堆栈 | 备注 | 
|---|---|---|---|
| ( | ( | 左括号压入堆栈。 | |
| 3 | 3 | ( | 数字添加到输出队列。 | 
| + | 3 | (,+ | 运算符压入堆栈。 | 
| 4 | 3,4 | (,+ | 数字添加到输出队列。 | 
| ) | 3,4,+ | ( | 右括号:弹出运算符 ( +) 到队列,直到找到左括号。弹出左括号。 | 
|  | 3,4,+ |  | 运算符压入堆栈(堆栈为空)。 | 
| 2 | 3,4,+,2 |  | 数字添加到输出队列。 | 
| - | 3,4,+,2, | - | 运算符:堆栈上的 优先级更高,弹出到队列。将-压入堆栈。 | 
| 5 | 3,4,+,2,,5 | - | 数字添加到输出队列。 | 
| 结束 | 3,4,+,2,,5,- | 输入结束:从堆栈中弹出剩余的运算符 ( -) 到队列。 | 
因此,中缀表达式 (3 + 4)  2 - 5 的 RPN 形式为:3 4 + 2 * 5 -。
总结
逆波兰表示法是一种优雅而高效的数学表达式书写和求值方法。尽管乍一看它可能不如中缀表示法直观,但 RPN 提供了几个优点,特别是对于计算机处理而言。它消除了歧义,不需要括号,并且可以使用简单的堆栈有效地求值。调度场算法提供了一种广泛使用且有效的方法,用于将中缀表达式转换为 RPN。
它的历史意义不可否认,在早期的袖珍计算器中发挥了重要作用,并且至今仍然是计算机科学中的一个相关概念,用于编译器和虚拟机中。虽然 RPN 在日常使用中不太常见,但它是一种有价值的工具和思维方式,可以更深入地了解数学表达式和计算机运算之间的关系。