引言
表达式求值是编译器、计算器和电子表格软件的核心功能。给定一个中缀表达式如 ( 3 + 5 \times (2 – 8) ),如何正确计算其结果?直接按从左到右的顺序计算会得到错误答案,因为忽略了运算符优先级和括号。Dijkstra提出的调度场算法(Shunting Yard Algorithm)使用双栈结构(操作数栈和运算符栈)巧妙地解决了这一问题。本文将深入讲解该算法原理,并用Java实现一个支持加减乘除、括号和负数的通用表达式求值器。
算法原理
调度场算法的核心思想:
- 操作数栈:存储待计算的数字
- 运算符栈:存储待处理的运算符
- 优先级规则:乘除优先级高于加减,括号改变优先级
- 遇到运算符时:比较栈顶运算符与新运算符的优先级,如果栈顶优先级更高或相等,则先计算栈顶运算
优先级定义
| 运算符 | 优先级 | 结合性 |
|---|---|---|
+, - |
1 | 左结合 |
*, / |
2 | 左结合 |
( |
0 | – |
Java实现
核心求值器
import java.util.Stack;
/**
* 中缀表达式求值器
* 支持 +, -, *, /, 括号 和 负数
*/
public class ExpressionEvaluator {
/**
* 计算中缀表达式的值
* @param expression 中缀表达式字符串,如 "3+5*(2-8)"
* @return 计算结果
*/
public static double evaluate(String expression) {
if (expression == null || expression.trim().isEmpty()) {
throw new IllegalArgumentException("表达式不能为空");
}
// 去除所有空格
expression = expression.replaceAll("\s+", "");
Stack<Double> operands = new Stack<>(); // 操作数栈
Stack<Character> operators = new Stack<>(); // 运算符栈
int i = 0;
int n = expression.length();
while (i < n) {
char ch = expression.charAt(i);
// 处理数字(包括小数)
if (Character.isDigit(ch) || ch == '.') {
StringBuilder numStr = new StringBuilder();
while (i < n && (Character.isDigit(expression.charAt(i)) || expression.charAt(i) == '.')) {
numStr.append(expression.charAt(i));
i++;
}
operands.push(Double.parseDouble(numStr.toString()));
continue; // i已经在内层循环中递增
}
// 处理负号:判断是减号运算符还是负数符号
if (ch == '-') {
// 负号的条件:在开头,或在 '(' 后,或在另一个运算符后
if (i == 0 || expression.charAt(i - 1) == '(' || isOperator(expression.charAt(i - 1))) {
// 这是一个负号,属于数字的一部分
StringBuilder numStr = new StringBuilder("-");
i++;
while (i < n && (Character.isDigit(expression.charAt(i)) || expression.charAt(i) == '.')) {
numStr.append(expression.charAt(i));
i++;
}
operands.push(Double.parseDouble(numStr.toString()));
continue;
}
}
// 处理左括号:直接入栈
if (ch == '(') {
operators.push(ch);
}
// 处理右括号:计算到匹配的左括号
else if (ch == ')') {
while (!operators.isEmpty() && operators.peek() != '(') {
applyTopOperator(operands, operators);
}
// 弹出左括号
if (!operators.isEmpty() && operators.peek() == '(') {
operators.pop();
} else {
throw new IllegalArgumentException("括号不匹配");
}
}
// 处理运算符
else if (isOperator(ch)) {
// 当栈顶运算符优先级 >= 当前运算符时,先计算栈顶
while (!operators.isEmpty() &&
precedence(operators.peek()) >= precedence(ch)) {
applyTopOperator(operands, operators);
}
operators.push(ch);
}
i++;
}
// 处理栈中剩余的运算符
while (!operators.isEmpty()) {
if (operators.peek() == '(' || operators.peek() == ')') {
throw new IllegalArgumentException("括号不匹配");
}
applyTopOperator(operands, operators);
}
// 最终结果
if (operands.size() != 1) {
throw new IllegalArgumentException("表达式格式错误");
}
return operands.pop();
}
/**
* 判断字符是否为运算符
*/
private static boolean isOperator(char ch) {
return ch == '+' || ch == '-' || ch == '*' || ch == '/';
}
/**
* 获取运算符优先级
*/
private static int precedence(char op) {
switch (op) {
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
case '(':
return 0; // 左括号优先级最低
default:
return -1;
}
}
/**
* 应用栈顶运算符进行计算
*/
private static void applyTopOperator(Stack<Double> operands, Stack<Character> operators) {
if (operands.size() < 2) {
throw new IllegalArgumentException("操作数不足");
}
double b = operands.pop(); // 右操作数
double a = operands.pop(); // 左操作数
char op = operators.pop();
double result;
switch (op) {
case '+':
result = a + b;
break;
case '-':
result = a - b;
break;
case '*':
result = a * b;
break;
case '/':
if (b == 0) {
throw new ArithmeticException("除数不能为零");
}
result = a / b;
break;
default:
throw new IllegalArgumentException("未知运算符: " + op);
}
operands.push(result);
}
}
扩展:支持函数调用
import java.util.*;
/**
* 增强版表达式求值器
* 支持常用数学函数:sin, cos, sqrt, pow, abs, log
*/
public class AdvancedExpressionEvaluator {
private static final Set<String> FUNCTIONS = new HashSet<>(
Arrays.asList("sin", "cos", "sqrt", "pow", "abs", "log")
);
public static double evaluate(String expression) {
expression = expression.replaceAll("\s+", "").toLowerCase();
Stack<Double> operands = new Stack<>();
Stack<String> operators = new Stack<>(); // 支持多字符运算符和函数名
int i = 0, n = expression.length();
while (i < n) {
char ch = expression.charAt(i);
// 处理数字
if (Character.isDigit(ch) || ch == '.') {
int j = i;
while (j < n && (Character.isDigit(expression.charAt(j)) || expression.charAt(j) == '.')) {
j++;
}
operands.push(Double.parseDouble(expression.substring(i, j)));
i = j;
continue;
}
// 处理函数名
if (Character.isLetter(ch)) {
int j = i;
while (j < n && Character.isLetter(expression.charAt(j))) {
j++;
}
String func = expression.substring(i, j);
if (FUNCTIONS.contains(func)) {
operators.push(func);
} else {
throw new IllegalArgumentException("未知函数: " + func);
}
i = j;
continue;
}
// 处理逗号(函数参数分隔符)
if (ch == ',') {
while (!operators.isEmpty() && !operators.peek().equals("(")) {
applyTopOperator(operands, operators);
}
i++;
continue;
}
// 处理左括号
if (ch == '(') {
operators.push("(");
}
// 处理右括号
else if (ch == ')') {
while (!operators.isEmpty() && !operators.peek().equals("(")) {
applyTopOperator(operands, operators);
}
if (!operators.isEmpty() && operators.peek().equals("(")) {
operators.pop(); // 弹出左括号
}
// 检查右括号前是否有函数
if (!operators.isEmpty() && FUNCTIONS.contains(operators.peek())) {
applyFunction(operands, operators.pop());
}
}
// 处理运算符
else if (isOperator(ch)) {
while (!operators.isEmpty() &&
precedence(operators.peek()) >= precedence(String.valueOf(ch))) {
applyTopOperator(operands, operators);
}
operators.push(String.valueOf(ch));
}
i++;
}
while (!operators.isEmpty()) {
applyTopOperator(operands, operators);
}
return operands.pop();
}
private static boolean isOperator(char ch) {
return ch == '+' || ch == '-' || ch == '*' || ch == '/';
}
private static int precedence(String op) {
if (op.equals("+") || op.equals("-")) return 1;
if (op.equals("*") || op.equals("/")) return 2;
return 0;
}
private static void applyTopOperator(Stack<Double> operands, Stack<String> operators) {
double b = operands.pop();
double a = operands.pop();
String op = operators.pop();
switch (op) {
case "+": operands.push(a + b); break;
case "-": operands.push(a - b); break;
case "*": operands.push(a * b); break;
case "/": operands.push(a / b); break;
}
}
private static void applyFunction(Stack<Double> operands, String func) {
double arg = operands.pop();
switch (func) {
case "sin": operands.push(Math.sin(arg)); break;
case "cos": operands.push(Math.cos(arg)); break;
case "sqrt": operands.push(Math.sqrt(arg)); break;
case "abs": operands.push(Math.abs(arg)); break;
case "log": operands.push(Math.log(arg)); break;
}
}
}
测试与演示
public class ExpressionEvaluatorDemo {
public static void main(String[] args) {
System.out.println("=== 基础表达式求值测试 ===\n");
String[] testExpressions = {
"3+5*(2-8)",
"10+2*6",
"100*(2+12)",
"100*(2+12)/14",
"(1+2)*(3+4)",
"-5+3",
"3+-2",
"(2.5+3.5)*4"
};
for (String expr : testExpressions) {
try {
double result = ExpressionEvaluator.evaluate(expr);
System.out.printf("%-20s = %.4f%n", expr, result);
} catch (Exception e) {
System.out.printf("%-20s 错误: %s%n", expr, e.getMessage());
}
}
System.out.println("\n=== 增强版(含函数)测试 ===\n");
String[] advancedTests = {
"sqrt(16)+3*2",
"abs(-5)+10",
"sin(0)+cos(0)"
};
for (String expr : advancedTests) {
try {
double result = AdvancedExpressionEvaluator.evaluate(expr);
System.out.printf("%-20s = %.4f%n", expr, result);
} catch (Exception e) {
System.out.printf("%-20s 错误: %s%n", expr, e.getMessage());
}
}
}
}
复杂度分析
| 指标 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | ( O(n) ) | 每个字符只被处理一次 |
| 空间复杂度 | ( O(n) ) | 栈的最坏情况深度 |
| 正确性 | 完备 | 正确处理优先级和括号嵌套 |
应用场景
- 计算器软件:Windows计算器、手机计算器的核心引擎
- 电子表格:Excel公式解析与计算
- 编译器:编译原理中语法分析阶段的中缀转后缀
- 数据库:SQL查询中的表达式计算
- 科学计算:MATLAB、Mathematica的表达式解析
总结
调度场算法通过巧妙地使用双栈结构,将复杂的中缀表达式求值问题转化为可线性处理的流程。本文实现的基础版和增强版求值器展示了算法的扩展性——从基础四则运算到函数调用,核心逻辑始终保持清晰。掌握这一算法是理解编译器前端和表达式引擎的基础。