Java 实例 – 利用堆栈将中缀表达式转换成后缀表达式
在日常编程中,我们经常会遇到表达式求值的问题,比如计算器、公式解析、编译器前端等场景。而表达式有多种写法,其中最常见的是中缀表达式,例如 3 + 4 * 5,它的特点是运算符在两个操作数之间。但计算机处理这种表达式时效率不高,因为需要考虑运算符优先级和括号。
这时候,后缀表达式(也叫逆波兰表达式)就派上用场了。它的形式是 3 4 5 * +,运算符放在操作数之后。这种写法的好处是:无需括号、无需考虑优先级,可以直接用栈结构从左到右顺序处理,非常高效。
今天我们就来通过一个经典的 Java 实例,深入理解如何用堆栈(Stack)将中缀表达式转换为后缀表达式。整个过程不仅锻炼了我们对数据结构的理解,也提升了对表达式处理的实战能力。
为什么需要中缀转后缀?
想象一下,你正在做一道数学题:(3 + 4) * 5。你看到括号,先算括号里的 3 + 4,再乘以 5。这个顺序是人为规定的,但计算机并不“懂”括号。它必须通过规则来判断。
中缀表达式虽然直观,但计算机处理起来复杂,因为:
- 运算符有优先级(如
*比+优先) - 括号改变了运算顺序
- 需要临时保存中间结果
而后缀表达式解决了这些问题。只要我们能将中缀表达式转换为后缀,就可以用一个简单的栈机制来求值。
这就像把“人话”翻译成“机器能听懂的话”——中缀是自然语言,后缀是机器语言。
核心思想:利用堆栈处理运算符优先级
转换的核心思想是:
遇到操作数就直接输出;遇到运算符,根据优先级决定是否压入栈,或者先弹出栈顶的高优先级运算符。
我们用一个栈来暂存运算符。栈的特点是“后进先出”,正好符合运算优先级的“先处理高优先级”的逻辑。
运算符优先级规则(关键)
| 运算符 | 优先级(数值越大越优先) |
|---|---|
| ( | 0 |
| + | 1 |
| - | 1 |
| * | 2 |
| / | 2 |
| ^ | 3 |
括号的优先级最低,目的是让括号内部的表达式先算完。当遇到右括号 ) 时,就要不断弹出栈顶元素,直到遇到左括号 ( 为止。
实现步骤详解
我们分三步来实现这个转换过程:
- 从左到右遍历中缀表达式中的每一个字符
- 根据字符类型进行不同处理
- 最终输出后缀表达式
下面是一个完整的 Java 实现,每一步都有详细注释。
import java.util.*;
public class InfixToPostfix {
// 判断是否为操作数(数字)
private static boolean isOperand(char ch) {
return Character.isDigit(ch);
}
// 判断是否为运算符
private static boolean isOperator(char ch) {
return ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '^';
}
// 获取运算符优先级
private static int getPrecedence(char operator) {
switch (operator) {
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
case '^':
return 3;
default:
return 0;
}
}
// 主方法:将中缀表达式转换为后缀表达式
public static String infixToPostfix(String infix) {
StringBuilder postfix = new StringBuilder(); // 存储结果后缀表达式
Stack<Character> operatorStack = new Stack<>(); // 用于暂存运算符的栈
// 遍历中缀表达式中的每个字符
for (int i = 0; i < infix.length(); i++) {
char ch = infix.charAt(i);
// 1. 如果是操作数(数字),直接添加到结果中
if (isOperand(ch)) {
postfix.append(ch);
}
// 2. 如果是左括号 '(',压入栈
else if (ch == '(') {
operatorStack.push(ch);
}
// 3. 如果是右括号 ')',弹出栈中元素直到遇到左括号
else if (ch == ')') {
while (!operatorStack.isEmpty() && operatorStack.peek() != '(') {
postfix.append(operatorStack.pop());
}
operatorStack.pop(); // 弹出左括号,不输出
}
// 4. 如果是运算符,处理优先级
else if (isOperator(ch)) {
// 弹出栈顶中优先级大于等于当前运算符的运算符
while (!operatorStack.isEmpty() &&
operatorStack.peek() != '(' && // 遇到左括号停止
getPrecedence(operatorStack.peek()) >= getPrecedence(ch)) {
postfix.append(operatorStack.pop());
}
// 当前运算符入栈
operatorStack.push(ch);
}
// 5. 空格跳过(可选处理)
else if (ch == ' ') {
continue;
}
}
// 4. 遍历结束后,将栈中剩余的运算符全部弹出
while (!operatorStack.isEmpty()) {
postfix.append(operatorStack.pop());
}
return postfix.toString();
}
// 测试方法
public static void main(String[] args) {
String infixExpression = "3 + 4 * 5";
String postfixExpression = infixToPostfix(infixExpression);
System.out.println("中缀表达式: " + infixExpression);
System.out.println("后缀表达式: " + postfixExpression);
}
}
关键代码逻辑拆解
我们来逐行分析几个关键点。
1. 操作数处理
if (isOperand(ch)) {
postfix.append(ch);
}
当遇到数字(如 3、4),直接加到结果字符串中。这就像把“数字”直接搬进输出区,不加任何处理。
2. 括号处理
else if (ch == '(') {
operatorStack.push(ch);
}
左括号直接压入栈。它是一个“标记”,表示括号内的表达式需要优先处理。
else if (ch == ')') {
while (!operatorStack.isEmpty() && operatorStack.peek() != '(') {
postfix.append(operatorStack.pop());
}
operatorStack.pop(); // 弹出左括号
}
遇到右括号时,不断弹出栈顶元素,直到遇到左括号为止。这个过程相当于“把括号里的内容先处理完”。
3. 运算符优先级处理(最核心)
while (!operatorStack.isEmpty() &&
operatorStack.peek() != '(' &&
getPrecedence(operatorStack.peek()) >= getPrecedence(ch)) {
postfix.append(operatorStack.pop());
}
operatorStack.push(ch);
这里的关键是:只有当栈顶运算符优先级大于等于当前运算符时,才弹出。
比如 3 + 4 * 5:
- 遇到
+,栈空,直接压入 - 遇到
*,栈顶是+,优先级1 < 2,所以*直接入栈 - 最终结果是
3 4 5 * +,正确!
如果写成 3 * 4 + 5,* 会先入栈,+ 时比较 * 优先级更高,所以先弹出 *,再压入 +,结果是 3 4 * 5 +,正确。
实际案例演示
我们来测试几个常见表达式:
| 中缀表达式 | 后缀表达式 |
|---|---|
3 + 4 * 5 |
3 4 5 * + |
(3 + 4) * 5 |
3 4 + 5 * |
3 + 4 + 5 |
3 4 + 5 + |
3 * 4 + 5 * 6 |
3 4 * 5 6 * + |
2 ^ 3 + 1 |
2 3 ^ 1 + |
这些结果都可以通过上述代码验证。我们也可以扩展支持小数、负数,但需要增加对负号的判断(比如 -3 是负数,不是减号)。
常见误区与调试建议
- 忽略空格:输入中可能有空格,比如
3 + 4。代码中我们用ch == ' '跳过,避免误判。 - 括号不匹配:如果输入
3 + (4 * 5,没有右括号,程序可能抛出异常。实际项目中应加校验。 - 负数处理:如
-3 + 4,第一个-是负号,不是运算符。需特殊处理(通常在解析时判断前一个字符是否为操作数或左括号)。 - 栈为空时弹出:在
pop()前一定要检查isEmpty(),否则会抛出异常。
小结与延伸思考
通过这个 Java 实例,我们不仅学会了如何将中缀表达式转为后缀表达式,更深入理解了堆栈在表达式处理中的核心作用。它像一个“临时工”,把高优先级的运算符先存起来,等条件成熟再“派上用场”。
这个技术广泛应用于:
- 编译器词法分析与语法分析
- 计算器类应用(如 Android、桌面计算器)
- 公式引擎(如 Excel 的表达式求值)
下一步,你可以尝试:
- 实现后缀表达式求值(只需一个栈)
- 支持小数和负数
- 添加错误处理机制(如括号不匹配、非法字符)
掌握这一技能,不仅能提升你的算法思维,还能让你在面试中脱颖而出。
最后提醒
Java 实例 – 利用堆栈将中缀表达式转换成后缀表达式,是一个兼具理论深度和实践价值的典型问题。它结合了数据结构(栈)、算法逻辑(优先级判断)和字符串处理,是学习编程进阶的绝佳切入点。
别怕复杂,一步步来。当你第一次看到 3 + 4 * 5 被正确转换为 3 4 5 * + 时,那种“原来如此”的成就感,是代码世界最美的风景。