Skip to content

表达式求值

为什么需要表达式求值

我们日常书写的算术表达式如 (3 + 4) * 5中缀表达式——运算符位于操作数中间。这种写法对人类直观,但对计算机并不友好:运算符有优先级,还有括号改变求值顺序,直接解析非常麻烦。

编译器和计算器的做法是:先将中缀表达式转换为后缀表达式(也叫逆波兰表达式),再对后缀表达式求值。这两个步骤都依赖,是 408 考研中栈的经典应用。

核心思想

三种表达式的区别在于运算符相对操作数的位置:

类型写法示例(中缀 a + b * c
中缀表达式运算符在操作数中间a + b * c
前缀表达式(波兰式)运算符在操作数前面+ a * b c
后缀表达式(逆波兰式)运算符在操作数后面a b c * +

核心要点:

  • 后缀表达式不需要括号,运算顺序由运算符出现的先后唯一确定
  • 中缀转后缀:使用一个运算符栈,按优先级规则决定何时弹出运算符
  • 后缀求值:使用一个操作数栈,遇到运算符就弹出栈顶两个操作数计算

运算符优先级(栈外/栈内):

运算符栈外优先级(isp)栈内优先级(icp)
(最高最低
* /
+ -
)最低

左括号 ( 入栈前优先级最高(保证能入栈),入栈后优先级最低(不会被普通运算符弹出,只有 ) 才能将它弹出)。

交互可视化

通过下方的交互动画,你可以逐步观察表达式求值的执行过程:

加载可视化中...

操作详解

中缀转后缀

算法思路:从左到右扫描中缀表达式,借助一个运算符栈,将运算符按正确的顺序输出到后缀表达式。

关键步骤:

  1. 遇到操作数:直接输出到后缀表达式
  2. 遇到 (:直接入栈
  3. 遇到 ):依次弹出栈顶运算符并输出,直到遇到 (,将 ( 弹出但不输出
  4. 遇到运算符:将栈中优先级 当前运算符的运算符依次弹出并输出,然后当前运算符入栈
  5. 扫描结束后,将栈中剩余运算符依次弹出并输出

手算示例:将 a + b * c - (d / e) 转换为后缀表达式

扫描元素动作运算符栈后缀输出
a输出a
+入栈+a
b输出+a b
*入栈(优先级高于 ++ *a b
c输出+ *a b c
-弹出 *+,入栈-a b c * +
(入栈- (a b c * +
d输出- (a b c * + d
/入栈- ( /a b c * + d
e输出- ( /a b c * + d e
)弹出 /,弹出 (-a b c * + d e /
结束弹出 -a b c * + d e / -

参考代码(C):

c
// 中缀表达式转后缀表达式
// infix: 输入的中缀表达式字符串
// postfix: 输出的后缀表达式字符串
void infixToPostfix(char* infix, char* postfix) {
    char stack[MAX_SIZE];  // 运算符栈
    int top = -1;
    int j = 0;  // postfix 的写入位置

    for (int i = 0; infix[i] != '\0'; i++) {
        char ch = infix[i];

        if (isOperand(ch)) {
            // 操作数直接输出
            postfix[j++] = ch;
        } else if (ch == '(') {
            // 左括号直接入栈
            stack[++top] = ch;
        } else if (ch == ')') {
            // 右括号:弹出直到遇到左括号
            while (top >= 0 && stack[top] != '(')
                postfix[j++] = stack[top--];
            top--;  // 弹出左括号
        } else {
            // 普通运算符:弹出优先级 >= 当前运算符的栈顶元素
            while (top >= 0 && stack[top] != '(' &&
                   priority(stack[top]) >= priority(ch))
                postfix[j++] = stack[top--];
            stack[++top] = ch;
        }
    }
    // 弹出栈中剩余运算符
    while (top >= 0)
        postfix[j++] = stack[top--];
    postfix[j] = '\0';
}

int priority(char op) {
    if (op == '*' || op == '/') return 2;
    if (op == '+' || op == '-') return 1;
    return 0;
}

后缀表达式求值

算法思路:从左到右扫描后缀表达式,使用一个操作数栈存放中间结果。

关键步骤:

  1. 遇到操作数:压入操作数栈
  2. 遇到运算符:弹出栈顶两个操作数,先弹出的是右操作数,后弹出的是左操作数,计算结果压回栈
  3. 扫描结束后,栈顶即为最终结果

手算示例:求值 3 4 2 * + 5 -

扫描元素动作操作数栈
3入栈3
4入栈3 4
2入栈3 4 2
*弹出 2、4,计算 4*2=8,入栈3 8
+弹出 8、3,计算 3+8=11,入栈11
5入栈11 5
-弹出 5、11,计算 11-5=6,入栈6

最终结果为 6

注意弹出顺序:先弹出的是右操作数,后弹出的是左操作数。对于 -/ 这类不满足交换律的运算符,顺序不能搞反,这是常见错误点。

参考代码(C):

c
// 后缀表达式求值
// postfix: 后缀表达式字符串(操作数为单个数字字符)
int evalPostfix(char* postfix) {
    int stack[MAX_SIZE];  // 操作数栈
    int top = -1;

    for (int i = 0; postfix[i] != '\0'; i++) {
        char ch = postfix[i];

        if (isdigit(ch)) {
            // 操作数入栈
            stack[++top] = ch - '0';
        } else {
            // 弹出两个操作数,注意顺序
            int right = stack[top--];
            int left  = stack[top--];
            int result;
            switch (ch) {
                case '+': result = left + right; break;
                case '-': result = left - right; break;
                case '*': result = left * right; break;
                case '/': result = left / right; break;
            }
            stack[++top] = result;
        }
    }
    return stack[top];
}

复杂度分析

操作时间复杂度说明
中缀转后缀O(n)每个字符最多入栈、出栈各一次
后缀表达式求值O(n)单次扫描,每个元素操作 O(1)
中缀直接求值O(n)需要两个栈(运算符栈 + 操作数栈)

空间复杂度:O(n),栈的最大深度与表达式长度成正比。

考研高频考点

  • ⭐ 给定中缀表达式,手算转换为后缀/前缀表达式(选择题/填空题高频)
  • ⭐ 给定后缀表达式,手算求值过程及结果(选择题高频)
  • ⭐ 中缀转后缀过程中运算符栈的变化(选择题/简答题)
  • ⭐ 操作数弹出顺序(左/右操作数)——除法和减法的易错点
  • 前缀表达式的求值方法(从右到左扫描,其余与后缀类似)
  • 中缀表达式直接求值的双栈算法(偶尔考)
  • 括号匹配与表达式合法性判断(栈的另一经典应用,常结合考察)