数据结构 - 表达式解析
表达式解析(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 算法思想)
将中缀表达式转换为后缀表达式的核心算法借助栈来实现,基本流程如下:
- 从左到右扫描中缀表达式
- 遇到操作数(数字/字母),直接输出
- 遇到运算符,将其与栈顶运算符比较优先级:
- 如果栈顶运算符优先级更高或相等,则弹出栈顶并输出
- 重复此过程,直到可以将当前运算符压入栈中
- 遇到左括号,直接压栈
- 遇到右括号,不断弹出栈顶运算符并输出,直到遇到左括号(左括号本身弹出但不输出)
- 扫描结束后,将栈中剩余的运算符依次弹出并输出
实例
#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;
}
#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;
}
#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;
}
#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;
}
