最近在看算法,看到这个逆波兰表达式,一起学习下:
逆波兰表达式(也称为后缀表达式)是一种数学表达式的书写方式,其中运算符位于操作数之后,无需使用括号即可明确运算顺序。它是**栈(Stack)**的经典应用场景。
📌 核心特点
-
运算符在操作数之后
- 传统表达式(中缀):
(2 + 3) * 4
- 逆波兰表达式(后缀):
2 3 + 4 *
- 传统表达式(中缀):
-
无括号,运算顺序由运算符位置决定
- 例如:
2 3 4 * +
表示2 + (3 * 4)
- 例如:
-
适合用栈计算
- 遇到数字入栈,遇到运算符弹出栈顶两个数计算,结果再入栈。
📌 与中缀表达式的对比
表达式类型 | 示例 | 运算顺序 |
---|---|---|
中缀表达式 | 3 + 4 * 2 | 需考虑运算符优先级和括号 |
逆波兰表达式 | 3 4 2 * + | 严格从左到右计算,无需括号 |
📌 计算逆波兰表达式的步骤
输入:["2", "3", "+", "4", "*"]
(即 2 3 + 4 *
)
步骤:
- 初始化空栈
stack = []
- 遍历表达式:
"2"
→ 数字 → 入栈 →stack = [2]
"3"
→ 数字 → 入栈 →stack = [2, 3]
"+"
→ 运算符 → 弹出3
和2
→ 计算2 + 3 = 5
→ 入栈 →stack = [5]
"4"
→ 数字 → 入栈 →stack = [5, 4]
"*"
→ 运算符 → 弹出4
和5
→ 计算5 * 4 = 20
→ 入栈 →stack = [20]
- 最终结果:
20
📌 代码实现(JavaScript)
function evalRPN(tokens) {
const stack = [];
for (const token of tokens) {
if (!isNaN(token)) { // 如果是数字
stack.push(Number(token));
} else { // 如果是运算符
const b = stack.pop();
const a = stack.pop();
switch (token) {
case "+": stack.push(a + b); break;
case "-": stack.push(a - b); break;
case "*": stack.push(a * b); break;
case "/": stack.push(Math.trunc(a / b)); break; // 向零取整
}
}
}
return stack.pop();
}
// 测试
console.log(evalRPN(["2", "3", "+", "4", "*"])); // 输出: 20
console.log(evalRPN(["4", "13", "5", "/", "+"])); // 输出: 6(因为 13 / 5 = 2.6 → 取整 2,4 + 2 = 6)
📌 中缀表达式转逆波兰表达式
算法步骤(Shunting-yard Algorithm)
- 初始化一个输出队列和一个运算符栈。
- 遍历中缀表达式:
- 遇到数字 → 加入输出队列。
- 遇到运算符 → 弹出栈顶优先级更高或相等的运算符,再入栈。
- 遇到
(
→ 入栈。 - 遇到
)
→ 弹出栈内运算符直到(
。
- 最后将栈内剩余运算符全部弹出。
示例:
中缀表达式:3 + 4 * 2 / (1 - 5)
逆波兰表达式:3 4 2 * 1 5 - / +
📌 逆波兰表达式的优势
- 无歧义:无需括号即可明确运算顺序。
- 高效计算:只需一次扫描,时间复杂度 O(n)。
- 适合计算机处理:早期计算器(如HP系列)使用RPN。
📌 常见应用场景
- 计算器程序(如HP科学计算器)。
- 编译器语法分析(解析算术表达式)。
- 算法题(如LeetCode 150. 逆波兰表达式求值)。
📌 总结
关键点 | 说明 |
---|---|
定义 | 运算符在操作数之后的表达式 |
计算方式 | 用栈结构,遇到数字入栈,遇到运算符弹出计算 |
转换方法 | Shunting-yard算法(中缀转后缀) |
优势 | 无括号、无优先级冲突、计算高效 |
逆波兰表达式是栈结构的经典应用,理解它对学习编译原理和算法很有帮助!