一、什么是中缀和后缀表达式
1.1 三种表达式
| 类型 | 示例 | 说明 |
|---|---|---|
| 中缀 | 3 + 4 * 2 |
运算符在两个操作数之间,人类习惯 |
| 前缀 | + 3 * 4 2 |
运算符在前面 |
| 后缀 | 3 4 2 * + |
运算符在后面,计算机容易处理 |
1.2 为什么用后缀表达式
计算 3+4*2 时,人类知道先乘后加。但计算机从左到右扫描,遇到 + 时不知道后面还有 *。
后缀表达式 3 4 2 * + 就没有这个问题:
-
遇到数字就压栈
-
遇到运算符就弹出两个数计算,结果压栈
-
不需要知道优先级,顺序扫描即可
二、计算后缀表达式
先学会计算后缀,再学转换。因为转换过程中也要用到这个逻辑。
2.1 算法思路
用栈存储数字:
-
从左到右扫描后缀表达式
-
遇到数字,压入栈
-
遇到运算符,弹出栈顶两个数字(先弹出右操作数,再弹出左操作数),计算后结果压栈
-
扫描结束,栈顶就是最终结果
示例 :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 转换算法
用栈存储运算符:
-
从左到右扫描中缀表达式
-
遇到数字,直接输出
-
遇到左括号
(,入栈 -
遇到右括号
),不断弹出栈顶运算符并输出,直到遇到左括号(左括号弹出但不输出) -
遇到运算符:
-
栈空或栈顶是左括号,直接入栈
-
否则,如果当前运算符优先级 > 栈顶运算符优先级,入栈
-
否则(优先级 <= 栈顶),弹出栈顶并输出,继续比较新栈顶
-
-
扫描结束后,弹出栈中所有运算符并输出
示例 :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) |
六、小结
这一篇我们用栈实现了完整的四则运算计算器:
| 内容 | 要点 |
|---|---|
| 后缀表达式 | 没有括号和优先级,计算机容易处理 |
| 中缀转后缀 | 用运算符栈,根据优先级决定是否弹出 |
| 后缀计算 | 用数字栈,遇到运算符就弹出两个数计算 |
| 完整计算器 | 两步合一,支持+ - * / 和括号 |
核心规则:
-
数字直接输出
-
左括号入栈
-
右括号弹出直到左括号
-
运算符:优先级高于栈顶才入栈,否则弹出
下一篇我们讲队列。
七、思考题
-
后缀表达式
5 1 2 + 4 * + 3 -的值是多少?手动计算一下。 -
中缀转后缀算法中,为什么要用
>=而不是>来比较优先级? -
如何让计算器支持负数和浮点数?
-
尝试实现一个支持乘方
^运算(右结合)的表达式求值器。
欢迎在评论区讨论你的答案。