现在位置: 首页 > C 语言数据结构与算法 > 正文

数据结构 - 表达式解析

表达式解析(Parsing Expressions)是栈这一数据结构最经典的应用场景。

表达式解析展示了如何利用栈来处理中缀、前缀和后缀表达式之间的转换以及表达式求值。


三种表达式形式

中缀表达式 A + B * C 转后缀表达式 — 步骤演示
步骤1读 A操作数,直接输出输出: A栈: 空
步骤2读 +运算符,压栈输出: A栈: +
步骤3读 B操作数,直接输出输出: AB栈: +
步骤4读 ** 优先级 > +,压栈输出: AB栈: +*
步骤5读 C操作数,直接输出输出: ABC栈: +*
步骤6结束弹出栈中所有运算符输出: ABC*+栈: 空
最终结果:A + B * C → A B C * +

在计算机中处理数学表达式时,有三种表示方式:

类型运算符位置示例求值难度括号需求
中缀表达式操作数之间(人习惯)A + B * C需要处理优先级和括号需要
前缀表达式(波兰式)操作数之前+ A * B C扫描一遍即可不需要
后缀表达式(逆波兰式)操作数之后A B C * +扫描一遍即可不需要

后缀表达式和前缀表达式最大的优势在于:求值过程中完全不需要考虑运算符优先级和括号,只需从左到右顺序扫描即可。


中缀转后缀算法(Shunting Yard 算法思想)

将中缀表达式转换为后缀表达式的核心算法借助栈来实现,基本流程如下:

  1. 从左到右扫描中缀表达式
  2. 遇到操作数(数字/字母),直接输出
  3. 遇到运算符,将其与栈顶运算符比较优先级:
    • 如果栈顶运算符优先级更高或相等,则弹出栈顶并输出
    • 重复此过程,直到可以将当前运算符压入栈中
  4. 遇到左括号,直接压栈
  5. 遇到右括号,不断弹出栈顶运算符并输出,直到遇到左括号(左括号本身弹出但不输出)
  6. 扫描结束后,将栈中剩余的运算符依次弹出并输出

实例

#include <stdio.h>
#include <ctype.h>   /* isalnum 函数 */
#include <string.h>

#define MAX 100

char stack[MAX];
int top = -1;

void push(char c) { stack[++top] = c; }
char pop() { return (top == -1) ? -1 : stack[top--]; }

/* 返回运算符优先级:数字越大优先级越高 */
int precedence(char op) {
    if (op == '+' || op == '-') return 1;  /* 加减优先级最低 */
    if (op == '*' || op == '/') return 2;  /* 乘除优先级较高 */
    if (op == '^') return 3;               /* 幂运算优先级最高 */
    return 0;  /* 不是运算符 */
}

/* 中缀表达式转后缀表达式
   参数:infix 输入的中缀表达式字符串
          postfix 输出的后缀表达式缓冲区 */

void infixToPostfix(char* infix, char* postfix) {
    int i = 0, j = 0;
    char c;

    while ((c = infix[i++]) != '\0') {
        /* 操作数(字母或数字):直接输出 */
        if (isalnum(c)) {
            postfix[j++] = c;
        }
        /* 左括号:直接压栈 */
        else if (c == '(') {
            push(c);
        }
        /* 右括号:弹出直到遇到左括号 */
        else if (c == ')') {
            while (top != -1 && stack[top] != '(') {
                postfix[j++] = pop();
            }
            pop();  /* 弹出左括号 '(',但不输出 */
        }
        /* 运算符:处理优先级 */
        else {
            while (top != -1 &&
                   precedence(stack[top]) >= precedence(c) &&
                   stack[top] != '(') {
                postfix[j++] = pop();
            }
            push(c);
        }
    }

    /* 将栈中剩余运算符全部弹出 */
    while (top != -1) {
        postfix[j++] = pop();
    }
    postfix[j] = '\0';  /* 字符串结尾 */
}

int main() {
    char infix[] = "A+B*C";
    char postfix[MAX];

    infixToPostfix(infix, postfix);
    printf("中缀: %s\n", infix);      /* 输出: 中缀: A+B*C */
    printf("后缀: %s\n", postfix);    /* 输出: 后缀: ABC*+ */
    return 0;
}

后缀表达式求值

后缀表达式 5 3 + 2 * 求值 — 步骤演示
步骤1读 5数字,入栈栈: 5
步骤2读 3数字,入栈栈: 5 3
步骤3读 +弹出 3 和 5,计算 5+3=8,结果入栈栈: 85+3=8
步骤4读 2数字,入栈栈: 8 2
步骤5读 *弹出 2 和 8,计算 8*2=16,结果入栈栈: 168*2=16
结果结束栈中只剩 16,即计算结果结果 = 16
后缀 53+2* = 16(等价于中缀 (5+3)*2)

有了后缀表达式之后,利用栈求值就非常直接:遇到操作数压栈,遇到运算符则弹出两个操作数计算后再压入。

实例

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>

#define MAX 100

int stack[MAX];
int top = -1;

void push(int val) { stack[++top] = val; }
int pop() { return stack[top--]; }

/* 计算后缀表达式的值
   参数:postfix 后缀表达式(操作数均为单个数字 0-9)
   返回值:表达式的计算结果 */

int evaluatePostfix(char* postfix) {
    int i = 0;
    char c;

    while ((c = postfix[i++]) != '\0') {
        /* 操作数:转换为数字后压栈 */
        if (isdigit(c)) {
            push(c - '0');  /* '3' 转为整数 3 */
        }
        /* 运算符:弹出两个操作数,计算后压回 */
        else {
            int b = pop();  /* 第二个操作数(右操作数) */
            int a = pop();  /* 第一个操作数(左操作数) */
            switch (c) {
                case '+': push(a + b); break;
                case '-': push(a - b); break;
                case '*': push(a * b); break;
                case '/': push(a / b); break;
            }
        }
    }
    return pop();  /* 栈中最后一个元素即为最终结果 */
}

int main() {
    /* 后缀表达式 "23+5*" 等价于中缀 "(2+3)*5" = 25 */
    char postfix[] = "23+5*";
    int result = evaluatePostfix(postfix);
    printf("后缀 %s = %d\n", postfix, result);  /* 输出: 后缀 23+5* = 25 */
    return 0;
}

后缀表达式求值时,操作数的弹出顺序非常关键:先弹出的是右操作数(b),后弹出的是左操作数(a)。这对于减法和除法等非对称运算尤为重要,顺序搞反会导致结果错误。


完整示例:中缀求值

将上述两步串联起来,就可以实现一个完整的计算器:中缀 → 后缀 → 求值。

实例

#include <stdio.h>
#include <ctype.h>

#define MAX 100

/* 运算符优先级辅助函数(同上),求值函数(同上),转换函数(同上) */
/* 为简洁起见,此处省略上述已定义的函数,实际使用时需包含它们 */

int main() {
    char infix[] = "5+3*2";            /* 中缀表达式 */
    char postfix[MAX];
    infixToPostfix(infix, postfix);    /* 步骤 1:转为后缀 */

    printf("中缀: %s\n", infix);       /* 输出: 中缀: 5+3*2 */
    printf("后缀: %s\n", postfix);     /* 输出: 后缀: 532*+ */

    int result = evaluatePostfix(postfix);  /* 步骤 2:求值 */
    printf("结果: %s = %d\n", infix, result);
    /* 输出: 结果: 5+3*2 = 11 */
    return 0;
}