逆波兰表示法:求值数学表达式的优雅替代方案
逆波兰表示法 (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 在日常使用中不太常见,但它是一种有价值的工具和思维方式,可以更深入地了解数学表达式和计算机运算之间的关系。