【数据结构与算法】第13篇:栈(三):中缀表达式转后缀表达式及计算

一、什么是中缀和后缀表达式

1.1 三种表达式

类型 示例 说明
中缀 3 + 4 * 2 运算符在两个操作数之间,人类习惯
前缀 + 3 * 4 2 运算符在前面
后缀 3 4 2 * + 运算符在后面,计算机容易处理

1.2 为什么用后缀表达式

计算 3+4*2 时,人类知道先乘后加。但计算机从左到右扫描,遇到 + 时不知道后面还有 *

后缀表达式 3 4 2 * + 就没有这个问题:

  • 遇到数字就压栈

  • 遇到运算符就弹出两个数计算,结果压栈

  • 不需要知道优先级,顺序扫描即可


二、计算后缀表达式

先学会计算后缀,再学转换。因为转换过程中也要用到这个逻辑。

2.1 算法思路

用栈存储数字:

  1. 从左到右扫描后缀表达式

  2. 遇到数字,压入栈

  3. 遇到运算符,弹出栈顶两个数字(先弹出右操作数,再弹出左操作数),计算后结果压栈

  4. 扫描结束,栈顶就是最终结果

示例3 4 2 * +

text

复制代码
扫描"3" → 栈: [3]
扫描"4" → 栈: [3, 4]
扫描"2" → 栈: [3, 4, 2]
扫描"*" → 弹出2和4,计算4*2=8,压栈 → 栈: [3, 8]
扫描"+" → 弹出8和3,计算3+8=11,压栈 → 栈: [11]
结果: 11

2.2 代码实现

c

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

#define MAX_SIZE 100

// 数字栈
typedef struct {
    double data[MAX_SIZE];
    int top;
} NumStack;

void initNumStack(NumStack *s) {
    s->top = -1;
}

int isNumStackEmpty(NumStack *s) {
    return s->top == -1;
}

int isNumStackFull(NumStack *s) {
    return s->top == MAX_SIZE - 1;
}

void pushNum(NumStack *s, double val) {
    if (isNumStackFull(s)) {
        printf("栈满\n");
        return;
    }
    s->data[++s->top] = val;
}

double popNum(NumStack *s) {
    if (isNumStackEmpty(s)) {
        printf("栈空\n");
        return 0;
    }
    return s->data[s->top--];
}

// 计算后缀表达式
double evalPostfix(const char *expr) {
    NumStack stack;
    initNumStack(&stack);
    
    char *token = strtok((char*)expr, " ");
    while (token != NULL) {
        // 如果是数字
        if (isdigit(token[0]) || (token[0] == '-' && isdigit(token[1]))) {
            pushNum(&stack, atof(token));
        }
        // 如果是运算符
        else {
            double right = popNum(&stack);
            double left = popNum(&stack);
            double result;
            
            switch (token[0]) {
                case '+': result = left + right; break;
                case '-': result = left - right; break;
                case '*': result = left * right; break;
                case '/': 
                    if (right == 0) {
                        printf("除数不能为0\n");
                        return 0;
                    }
                    result = left / right; 
                    break;
                default:
                    printf("未知运算符: %s\n", token);
                    return 0;
            }
            pushNum(&stack, result);
        }
        token = strtok(NULL, " ");
    }
    return popNum(&stack);
}

int main() {
    char expr[] = "3 4 2 * +";
    printf("%s = %.2f\n", expr, evalPostfix(expr));
    
    char expr2[] = "10 5 / 2 3 * +";
    printf("%s = %.2f\n", expr2, evalPostfix(expr2));
    
    return 0;
}

运行结果:

text

复制代码
3 4 2 * + = 11.00
10 5 / 2 3 * + = 8.00

三、中缀转后缀

3.1 运算符优先级

运算符 优先级
() 最高
* / 2
+ - 1

3.2 转换算法

用栈存储运算符:

  1. 从左到右扫描中缀表达式

  2. 遇到数字,直接输出

  3. 遇到左括号 (,入栈

  4. 遇到右括号 ),不断弹出栈顶运算符并输出,直到遇到左括号(左括号弹出但不输出)

  5. 遇到运算符

    • 栈空或栈顶是左括号,直接入栈

    • 否则,如果当前运算符优先级 > 栈顶运算符优先级,入栈

    • 否则(优先级 <= 栈顶),弹出栈顶并输出,继续比较新栈顶

  6. 扫描结束后,弹出栈中所有运算符并输出

示例3 + 4 * 2

text

复制代码
扫描"3" → 输出: 3
扫描"+" → 栈空,入栈 → 栈: [+]
扫描"4" → 输出: 3 4
扫描"*" → *优先级 > +,入栈 → 栈: [+ *]
扫描"2" → 输出: 3 4 2
结束 → 弹出栈: 输出 * + 
最终: 3 4 2 * +

示例(1 + 2) * 3

text

复制代码
扫描"(" → 入栈 → 栈: [(]
扫描"1" → 输出: 1
扫描"+" → 栈顶是(,入栈 → 栈: [( +]
扫描"2" → 输出: 1 2
扫描")" → 弹出+并输出 → 输出: 1 2 +,弹出(丢弃
扫描"*" → 栈空,入栈 → 栈: [*]
扫描"3" → 输出: 1 2 + 3
结束 → 弹出* → 输出: 1 2 + 3 *

3.3 代码实现

c

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

#define MAX_SIZE 100

// 运算符栈
typedef struct {
    char data[MAX_SIZE];
    int top;
} CharStack;

void initCharStack(CharStack *s) {
    s->top = -1;
}

int isCharStackEmpty(CharStack *s) {
    return s->top == -1;
}

int isCharStackFull(CharStack *s) {
    return s->top == MAX_SIZE - 1;
}

void pushChar(CharStack *s, char val) {
    if (isCharStackFull(s)) return;
    s->data[++s->top] = val;
}

char popChar(CharStack *s) {
    if (isCharStackEmpty(s)) return '\0';
    return s->data[s->top--];
}

char peekChar(CharStack *s) {
    if (isCharStackEmpty(s)) return '\0';
    return s->data[s->top];
}

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

// 判断是否为运算符
int isOperator(char c) {
    return c == '+' || c == '-' || c == '*' || c == '/';
}

// 中缀转后缀
void infixToPostfix(const char *infix, char *postfix) {
    CharStack stack;
    initCharStack(&stack);
    int pos = 0;
    
    for (int i = 0; infix[i] != '\0'; i++) {
        char ch = infix[i];
        
        // 跳过空格
        if (ch == ' ') continue;
        
        // 数字:直接输出
        if (isdigit(ch)) {
            while (isdigit(infix[i]) || infix[i] == '.') {
                postfix[pos++] = infix[i++];
            }
            postfix[pos++] = ' ';  // 分隔符
            i--;  // 回退一个字符
        }
        // 左括号:入栈
        else if (ch == '(') {
            pushChar(&stack, ch);
        }
        // 右括号:弹出直到左括号
        else if (ch == ')') {
            while (!isCharStackEmpty(&stack) && peekChar(&stack) != '(') {
                postfix[pos++] = popChar(&stack);
                postfix[pos++] = ' ';
            }
            popChar(&stack);  // 弹出左括号
        }
        // 运算符
        else if (isOperator(ch)) {
            while (!isCharStackEmpty(&stack) && peekChar(&stack) != '(' &&
                   priority(peekChar(&stack)) >= priority(ch)) {
                postfix[pos++] = popChar(&stack);
                postfix[pos++] = ' ';
            }
            pushChar(&stack, ch);
        }
    }
    
    // 弹出栈中剩余运算符
    while (!isCharStackEmpty(&stack)) {
        postfix[pos++] = popChar(&stack);
        postfix[pos++] = ' ';
    }
    postfix[pos - 1] = '\0';  // 去掉最后多余的空格
}

int main() {
    char infix1[] = "3+4*2";
    char infix2[] = "(1+2)*3";
    char infix3[] = "10/(3-1)";
    char postfix[MAX_SIZE];
    
    infixToPostfix(infix1, postfix);
    printf("中缀: %s\n后缀: %s\n", infix1, postfix);
    
    infixToPostfix(infix2, postfix);
    printf("中缀: %s\n后缀: %s\n", infix2, postfix);
    
    infixToPostfix(infix3, postfix);
    printf("中缀: %s\n后缀: %s\n", infix3, postfix);
    
    return 0;
}

运行结果:

text

复制代码
中缀: 3+4*2
后缀: 3 4 2 * +
中缀: (1+2)*3
后缀: 1 2 + 3 *
中缀: 10/(3-1)
后缀: 10 3 1 - /

四、完整计算器实现

把中缀转后缀和后缀计算结合起来,实现一个完整计算器。

c

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

#define MAX_SIZE 100

// 运算符栈
typedef struct {
    char data[MAX_SIZE];
    int top;
} OpStack;

void initOpStack(OpStack *s) { s->top = -1; }
int isOpEmpty(OpStack *s) { return s->top == -1; }
int isOpFull(OpStack *s) { return s->top == MAX_SIZE - 1; }
void pushOp(OpStack *s, char val) { if (!isOpFull(s)) s->data[++s->top] = val; }
char popOp(OpStack *s) { return isOpEmpty(s) ? '\0' : s->data[s->top--]; }
char peekOp(OpStack *s) { return isOpEmpty(s) ? '\0' : s->data[s->top]; }

// 数字栈
typedef struct {
    double data[MAX_SIZE];
    int top;
} NumStack;

void initNumStack(NumStack *s) { s->top = -1; }
int isNumEmpty(NumStack *s) { return s->top == -1; }
int isNumFull(NumStack *s) { return s->top == MAX_SIZE - 1; }
void pushNum(NumStack *s, double val) { if (!isNumFull(s)) s->data[++s->top] = val; }
double popNum(NumStack *s) { return isNumEmpty(s) ? 0 : s->data[s->top--]; }

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

int isOperator(char c) {
    return c == '+' || c == '-' || c == '*' || c == '/';
}

// 计算后缀表达式
double evalPostfix(const char *postfix) {
    NumStack stack;
    initNumStack(&stack);
    
    for (int i = 0; postfix[i] != '\0'; i++) {
        if (isdigit(postfix[i])) {
            double num = 0;
            while (isdigit(postfix[i])) {
                num = num * 10 + (postfix[i] - '0');
                i++;
            }
            pushNum(&stack, num);
        }
        else if (isOperator(postfix[i])) {
            double right = popNum(&stack);
            double left = popNum(&stack);
            double result;
            switch (postfix[i]) {
                case '+': result = left + right; break;
                case '-': result = left - right; break;
                case '*': result = left * right; break;
                case '/': result = left / right; break;
                default: return 0;
            }
            pushNum(&stack, result);
        }
    }
    return popNum(&stack);
}

// 中缀转后缀并计算
double calculate(const char *infix) {
    OpStack stack;
    initOpStack(&stack);
    char postfix[MAX_SIZE];
    int pos = 0;
    
    for (int i = 0; infix[i] != '\0'; i++) {
        char ch = infix[i];
        if (ch == ' ') continue;
        
        if (isdigit(ch)) {
            while (isdigit(infix[i]) || infix[i] == '.') {
                postfix[pos++] = infix[i++];
            }
            postfix[pos++] = ' ';
            i--;
        }
        else if (ch == '(') {
            pushOp(&stack, ch);
        }
        else if (ch == ')') {
            while (!isOpEmpty(&stack) && peekOp(&stack) != '(') {
                postfix[pos++] = popOp(&stack);
                postfix[pos++] = ' ';
            }
            popOp(&stack);
        }
        else if (isOperator(ch)) {
            while (!isOpEmpty(&stack) && peekOp(&stack) != '(' &&
                   priority(peekOp(&stack)) >= priority(ch)) {
                postfix[pos++] = popOp(&stack);
                postfix[pos++] = ' ';
            }
            pushOp(&stack, ch);
        }
    }
    
    while (!isOpEmpty(&stack)) {
        postfix[pos++] = popOp(&stack);
        postfix[pos++] = ' ';
    }
    postfix[pos - 1] = '\0';
    
    printf("后缀表达式: %s\n", postfix);
    return evalPostfix(postfix);
}

int main() {
    char expr[MAX_SIZE];
    
    printf("请输入表达式(支持+ - * / 和括号): ");
    fgets(expr, MAX_SIZE, stdin);
    expr[strcspn(expr, "\n")] = '\0';
    
    double result = calculate(expr);
    printf("结果: %.2f\n", result);
    
    return 0;
}

运行效果:

text

复制代码
请输入表达式(支持+ - * / 和括号): (1+2)*(3+4)
后缀表达式: 1 2 + 3 4 + * 
结果: 21.00

五、复杂度分析

操作 时间复杂度 空间复杂度
中缀转后缀 O(n) O(n)
后缀计算 O(n) O(n)
整体 O(n) O(n)

六、小结

这一篇我们用栈实现了完整的四则运算计算器:

内容 要点
后缀表达式 没有括号和优先级,计算机容易处理
中缀转后缀 用运算符栈,根据优先级决定是否弹出
后缀计算 用数字栈,遇到运算符就弹出两个数计算
完整计算器 两步合一,支持+ - * / 和括号

核心规则

  • 数字直接输出

  • 左括号入栈

  • 右括号弹出直到左括号

  • 运算符:优先级高于栈顶才入栈,否则弹出

下一篇我们讲队列。


七、思考题

  1. 后缀表达式 5 1 2 + 4 * + 3 - 的值是多少?手动计算一下。

  2. 中缀转后缀算法中,为什么要用 >= 而不是 > 来比较优先级?

  3. 如何让计算器支持负数和浮点数?

  4. 尝试实现一个支持乘方 ^ 运算(右结合)的表达式求值器。

欢迎在评论区讨论你的答案。

相关推荐
happymaker06262 小时前
servlet、jsp、请求转发、重定向的一些个人理解
java·开发语言·servlet
another heaven2 小时前
【软考 IDEF系列方法:从概念到核心差异】
数据结构
于先生吖2 小时前
国际版答题系统 JAVA 源码实战指南
java·开发语言
码界筑梦坊2 小时前
354-基于Python的全国水稻数据可视化分析系统
开发语言·python·信息可视化·数据分析·flask·bootstrap·毕业设计
码界筑梦坊2 小时前
336-基于Python的肺癌数据可视化分析预测系统
开发语言·python·信息可视化·数据分析·django·vue·毕业设计
章鱼丸-2 小时前
DAY40 训练与测试规范写法
人工智能·算法·机器学习
AI科技星2 小时前
基于四维时空光速不变公设的量子几何与量子力学本质全维度推导验证
开发语言·人工智能·opencv·计算机视觉·数学建模·r语言
不会写DN2 小时前
Go 中最主流 JWT 库 jwt -go
开发语言·后端·golang
简单~2 小时前
C++ 函数模板完全指南
c++·函数模板