栈的五种经典应用
一、数制转换
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_SIZE 100
// 栈的基本结构
typedef struct {
int data[MAX_SIZE];
int top;
} Stack;
void initStack(Stack* s) { s->top = -1; }
bool isEmpty(Stack* s) { return s->top == -1; }
bool isFull(Stack* s) { return s->top == MAX_SIZE - 1; }
void push(Stack* s, int value) {
if (isFull(s)) return;
s->data[++s->top] = value;
}
int pop(Stack* s) {
if (isEmpty(s)) return -1;
return s->data[s->top--];
}
// 十进制转任意进制(2-16进制)
void decimalToBase(int decimal, int base) {
Stack s;
initStack(&s);
printf("%d(10) = ", decimal);
// 处理0的特殊情况
if (decimal == 0) {
printf("0");
return;
}
// 除基取余,结果压栈
while (decimal > 0) {
int remainder = decimal % base;
push(&s, remainder);
decimal /= base;
}
// 逆序输出:弹栈并转换数字为字符
char digits[] = "0123456789ABCDEF";
while (!isEmpty(&s)) {
int digit = pop(&s);
printf("%c", digits[digit]);
}
printf("(%d)\n", base);
}
// 任意进制转十进制
int baseToDecimal(char* number, int base) {
int result = 0;
int length = 0;
// 计算数字长度
while (number[length] != '\0') length++;
// 从最高位开始计算
for (int i = 0; i < length; i++) {
char ch = number[i];
int digit;
// 处理0-9
if (ch >= '0' && ch <= '9') {
digit = ch - '0';
}
// 处理A-F
else if (ch >= 'A' && ch <= 'F') {
digit = ch - 'A' + 10;
}
// 处理a-f
else if (ch >= 'a' && ch <= 'f') {
digit = ch - 'a' + 10;
} else {
return -1; // 非法字符
}
// 按权展开
result = result * base + digit;
}
return result;
}
void numberConversionDemo() {
printf(" 数制转换\n");
// 转换表
int testCases[] = {255, 1024, 42, 0, 100};
int bases[] = {2, 8, 16};
printf("十进制 → 其他进制:\n");
printf("------------------\n");
for (int i = 0; i < 5; i++) {
printf("\n十进制: %d\n", testCases[i]);
for (int j = 0; j < 3; j++) {
printf(" ");
decimalToBase(testCases[i], bases[j]);
}
}
printf("\n\n其他进制 → 十进制:\n");
printf("------------------\n");
char* hexNumbers[] = {"FF", "3A", "100", "7FFF"};
for (int i = 0; i < 4; i++) {
int decimal = baseToDecimal(hexNumbers[i], 16);
printf("%s(16) = %d(10)\n", hexNumbers[i], decimal);
}
}
转换过程可视化
以255转16进制为例:
初始:decimal = 255, base = 16 第1步:255 % 16 = 15 (F) → 压栈 [15] 255 / 16 = 15 第2步:15 % 16 = 15 (F) → 压栈 [15, 15] 15 / 16 = 0 结束:decimal = 0 弹栈输出:15(F) → 15(F) 结果:FF
为什么?
需要先计算完所有余数,再反向输出。要么用数组存起来再反向遍历,要么用递归。
二、括号匹配
#include <stdbool.h>
#include <string.h>
// 检查括号匹配的核心函数
bool isParenthesisBalanced(char* expression) {
Stack s;
initStack(&s);
for (int i = 0; expression[i] != '\0'; i++) {
char ch = expression[i];
// 左括号:压栈
if (ch == '(' || ch == '{' || ch == '[') {
push(&s, ch);
printf(" 位置 %d: 遇到 '%c',压栈\n", i, ch);
}
// 右括号:检查匹配(兑现承诺)
else if (ch == ')' || ch == '}' || ch == ']') {
if (isEmpty(&s)) {
printf(" 错误:位置 %d 的 '%c' 没有对应的左括号\n", i, ch);
return false;
}
char topChar = pop(&s);
// 检查是否匹配
bool isMatch = false;
if ((ch == ')' && topChar == '(') ||
(ch == '}' && topChar == '{') ||
(ch == ']' && topChar == '[')) {
isMatch = true;
}
if (!isMatch) {
printf(" 错误:位置 %d 的 '%c' 与栈顶的 '%c' 不匹配\n",
i, ch, topChar);
return false;
}
printf(" 位置 %d: 遇到 '%c',与栈顶的 '%c' 匹配,弹栈\n", i, ch, topChar);
}
}
// 最后检查栈是否为空
if (!isEmpty(&s)) {
printf(" 错误:表达式结束,但栈中还有未匹配的左括号\n");
return false;
}
return true;
}
void parenthesisCheckingDemo() {
printf("\n🧾 括号匹配检查\n");
char* testCases[] = {
// 正确案例
"(a + b) * (c - d)",
"{[(a + b) * c] - d}",
"function() { return [1, 2, 3]; }",
// 错误案例
"(a + b)) * c", // 多余右括号
"((a + b) * c", // 缺少右括号
"{[(a + b]) * c}", // 括号类型不匹配
"a + b) * (c - d", // 左右不匹配
};
for (int i = 0; i < 7; i++) {
printf("\n测试用例 %d:\n", i + 1);
printf("表达式:%s\n", testCases[i]);
checkExpressionWithDetails(testCases[i]);
printf("---\n");
}
// HTML标签匹配(扩展应用)
printf("\nHTML标签匹配检查:\n");
char* html = "<div><p>Hello</p></div>";
printf("检查HTML:%s\n", html);
// 简化版HTML标签检查(实际会更复杂)
Stack tagStack;
initStack(&tagStack);
for (int i = 0; html[i] != '\0'; i++) {
if (html[i] == '<' && html[i+1] != '/') {
// 开标签:<div>, <p>
push(&tagStack, html[i+1]); // 简单处理,只存标签首字母
}
else if (html[i] == '<' && html[i+1] == '/') {
// 闭标签:</div>, </p>
if (isEmpty(&tagStack)) {
printf("HTML错误:多余的闭标签\n");
break;
}
pop(&tagStack);
}
}
if (isEmpty(&tagStack)) {
printf("HTML标签匹配正确!\n");
} else {
printf("HTML错误:有未闭合的标签\n");
}
}
栈如何工作?
以表达式
{[(a+b)]}为例:text
输入: { [ ( a + b ) ] } 步骤 字符 栈状态 操作 1 { { 压栈{ 2 [ {[ 压栈[ 3 ( {[( 压栈( 4 ) {[ 弹出(,匹配成功 5 ] { 弹出[,匹配成功 6 } 空 弹出{,匹配成功
三、行编辑程序
c
#include <string.h>
#define LINE_MAX 1000
// 行编辑程序
void lineEditor() {
Stack inputStack; // 输入栈
Stack undoStack; // 撤销栈(用于#操作)
initStack(&inputStack);
initStack(&undoStack);
printf("行编辑程序(按Enter结束,@删除整行,#删除前一个字符)\n");
printf("输入: ");
char ch;
while ((ch = getchar()) != '\n' && ch != EOF) {
switch (ch) {
case '@': // 删除整行
printf("\n删除整行,重新输入: ");
while (!isEmpty(&inputStack)) {
pop(&inputStack);
}
break;
case '#': // 删除前一个字符
if (!isEmpty(&inputStack)) {
char deleted = pop(&inputStack);
push(&undoStack, deleted); // 保存到撤销栈
printf("\b \b"); // 回退光标并清除字符
}
break;
case '$': // 撤销最后一次删除
if (!isEmpty(&undoStack)) {
char restored = pop(&undoStack);
push(&inputStack, restored);
putchar(restored);
}
break;
default: // 正常字符
push(&inputStack, ch);
putchar(ch);
break;
}
}
// 输出最终结果
printf("\n\n最终结果: ");
// 需要将栈内容反转输出
Stack temp;
initStack(&temp);
// 转移到临时栈(反转)
while (!isEmpty(&inputStack)) {
push(&temp, pop(&inputStack));
}
// 输出
char finalLine[LINE_MAX];
int index = 0;
while (!isEmpty(&temp)) {
finalLine[index++] = pop(&temp);
}
finalLine[index] = '\0';
printf("%s\n", finalLine);
}
// 完整的行编辑器(支持多行)
void advancedLineEditor() {
printf("命令说明:\n");
printf(" @ - 删除当前行\n");
printf(" # - 删除前一个字符\n");
printf(" $ - 撤销最后一次删除\n");
printf(" %% - 显示当前内容\n");
printf(" ^ - 保存并退出\n");
printf("----------------------------\n");
Stack mainStack;
Stack undoStack;
initStack(&mainStack);
initStack(&undoStack);
char ch;
int lineCount = 1;
printf("\n[第1行] ");
while ((ch = getchar()) != '^') {
if (ch == '\n') {
// 换行
push(&mainStack, '\n');
lineCount++;
printf("\n[第%d行] ", lineCount);
continue;
}
switch (ch) {
case '@':
// 删除当前行:找到上一个换行符
printf("\n删除当前行...\n");
while (!isEmpty(&mainStack) && peek(&mainStack) != '\n') {
push(&undoStack, pop(&mainStack));
}
printf("[第%d行] ", lineCount--);
break;
case '#':
if (!isEmpty(&mainStack)) {
char deleted = pop(&mainStack);
push(&undoStack, deleted);
printf("\b \b"); // 清除显示
}
break;
case '$':
if (!isEmpty(&undoStack)) {
char restored = pop(&undoStack);
push(&mainStack, restored);
putchar(restored);
}
break;
case '%':
// 显示当前内容
printf("\n\n当前内容:\n");
displayStackContent(&mainStack);
printf("\n[第%d行] ", lineCount);
break;
default:
push(&mainStack, ch);
putchar(ch);
break;
}
}
// 保存最终结果
printf("\n\n📄 最终文档内容:\n");
printf("================\n");
displayStackContent(&mainStack);
}
// 辅助函数:显示栈内容
void displayStackContent(Stack* s) {
Stack temp;
initStack(&temp);
// 复制到临时栈
for (int i = 0; i <= s->top; i++) {
push(&temp, s->data[i]);
}
// 输出
while (!isEmpty(&temp)) {
char ch = pop(&temp);
putchar(ch);
}
}
工作原理
用户输入: whil#hile (true) { printf("Hello"); }@int main() { return 0; } 处理过程: 1. 输入"whil" → 栈: whil 2. 输入"#" → 删除'l' → 栈: whi 3. 输入"hile (true)..." → 栈: while (true) { printf("Hello"); } 4. 输入"@" → 删除整行 → 栈: 空 5. 输入"int main()..." → 栈: int main() { return 0; } 最终结果: int main() { return 0; }
四、迷宫求解
深度优先搜索
#include <stdbool.h>
#define MAZE_SIZE 10
// 迷宫定义(0=路,1=墙,2=起点,3=终点)
int maze[MAZE_SIZE][MAZE_SIZE] = {
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
{1, 2, 0, 1, 0, 0, 0, 1, 0, 1},
{1, 0, 0, 1, 0, 0, 0, 1, 0, 1},
{1, 0, 0, 0, 0, 1, 1, 0, 0, 1},
{1, 0, 1, 1, 1, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 1, 0, 0, 0, 0, 1},
{1, 0, 1, 0, 0, 0, 1, 0, 0, 1},
{1, 0, 1, 1, 1, 0, 1, 1, 0, 1},
{1, 3, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
};
// 位置结构
typedef struct {
int x;
int y;
int direction; // 0:上, 1:右, 2:下, 3:左
} Position;
// 迷宫求解
void solveMazeWithStack() {
Position start = {1, 1, 0}; // 起点
Position end = {8, 1}; // 终点
Stack pathStack;
initStack(&pathStack);
// 访问标记
bool visited[MAZE_SIZE][MAZE_SIZE] = {false};
printf("起点: (%d, %d), 终点: (%d, %d)\n\n", start.x, start.y, end.x, end.y);
// 起点入栈
push(&pathStack, start);
visited[start.x][start.y] = true;
int steps = 0;
bool found = false;
while (!isEmpty(&pathStack) && !found) {
Position current = peek(&pathStack);
printf("步骤 %d: 当前位置 (%d, %d)\n", ++steps, current.x, current.y);
// 检查是否到达终点
if (current.x == end.x && current.y == end.y) {
found = true;
break;
}
// 尝试四个方向
int directions[4][2] = {
{-1, 0}, // 上
{0, 1}, // 右
{1, 0}, // 下
{0, -1} // 左
};
bool moved = false;
for (int dir = current.direction; dir < 4; dir++) {
int newX = current.x + directions[dir][0];
int newY = current.y + directions[dir][1];
// 检查新位置是否可行
if (newX >= 0 && newX < MAZE_SIZE &&
newY >= 0 && newY < MAZE_SIZE &&
maze[newX][newY] != 1 && // 不是墙
!visited[newX][newY]) { // 未访问过
// 更新当前方向
Position updated = current;
updated.direction = dir + 1;
pop(&pathStack);
push(&pathStack, updated);
// 移动到新位置
Position newPos = {newX, newY, 0};
push(&pathStack, newPos);
visited[newX][newY] = true;
printf(" → 移动到 (%d, %d)\n", newX, newY);
moved = true;
break;
}
}
// 如果无路可走,回溯
if (!moved) {
pop(&pathStack);
printf(" ← 回溯到上一个位置\n");
}
}
if (found) {
printf("\n找到路径(从起点到终点):\n");
printPath(&pathStack);
} else {
printf("未找到路径\n");
}
}
// 打印路径
void printPath(Stack* s) {
Stack temp;
initStack(&temp);
// 复制路径到临时栈(为了正向显示)
for (int i = 0; i <= s->top; i++) {
Position pos = s->data[i];
push(&temp, pos);
}
// 显示路径
int step = 0;
while (!isEmpty(&temp)) {
Position pos = pop(&temp);
printf("步骤 %d: (%d, %d)\n", ++step, pos.x, pos.y);
// 在迷宫中标记路径
if (maze[pos.x][pos.y] == 0) {
maze[pos.x][pos.y] = 4; // 路径标记
}
}
// 打印带路径的迷宫
printf("\n迷宫地图(★=起点,⚑=终点,●=路径):\n");
for (int i = 0; i < MAZE_SIZE; i++) {
for (int j = 0; j < MAZE_SIZE; j++) {
if (maze[i][j] == 1) printf("██");
else if (maze[i][j] == 2) printf("★ ");
else if (maze[i][j] == 3) printf("⚑ ");
else if (maze[i][j] == 4) printf("● ");
else printf(" ");
}
printf("\n");
}
}
// 可视化演示
void mazeDemo() {
printf("\n迷宫求解演示\n");
printf("===============\n");
printf("初始迷宫:\n");
for (int i = 0; i < MAZE_SIZE; i++) {
for (int j = 0; j < MAZE_SIZE; j++) {
if (maze[i][j] == 1) printf("█");
else if (maze[i][j] == 2) printf("★ ");
else if (maze[i][j] == 3) printf("⚑ ");
else printf(" ");
}
printf("\n");
}
printf("\n");
solveMazeWithStack();
}
栈在迷宫中的作用
栈实现的是深度优先搜索(DFS):
前进:遇到岔路时,选择一条路走,其他岔路位置压栈
死路:走到死胡同时,弹栈回到上一个岔路
回溯:尝试其他未走过的路径
搜索过程示例
text
起点(1,1) → (1,2) → (1,3) ✗(墙) ↳ 回溯到(1,2) → (2,2) → ...
五、表达式求值
#include <ctype.h>
#include <math.h>
// 运算符优先级
int precedence(char op) {
switch (op) {
case '+':
case '-': return 1;
case '*':
case '/': return 2;
case '^': return 3;
default: return 0;
}
}
// 执行运算
double applyOperator(double a, double b, char op) {
switch (op) {
case '+': return a + b;
case '-': return a - b;
case '*': return a * b;
case '/':
if (b == 0) {
printf("错误:除数为零!\n");
return 0;
}
return a / b;
case '^': return pow(a, b);
default: return 0;
}
}
// 表达式求值
double evaluateExpression(char* expression) {
Stack values; // 值栈
Stack ops; // 运算符栈
initStack(&values);
initStack(&ops);
printf("计算表达式: %s\n", expression);
for (int i = 0; expression[i] != '\0'; i++) {
char ch = expression[i];
// 跳过空格
if (ch == ' ') continue;
// 数字:读取完整数字
if (isdigit(ch) || ch == '.') {
double num = 0;
double fraction = 0.1;
bool hasDecimal = false;
while (i < strlen(expression) &&
(isdigit(expression[i]) || expression[i] == '.')) {
if (expression[i] == '.') {
hasDecimal = true;
} else {
if (!hasDecimal) {
num = num * 10 + (expression[i] - '0');
} else {
num = num + (expression[i] - '0') * fraction;
fraction *= 0.1;
}
}
i++;
}
i--; // 回退一个字符
push(&values, num);
printf(" 读取数字: %.2f\n", num);
}
// 左括号
else if (ch == '(') {
push(&ops, ch);
printf(" 遇到 '(',压入运算符栈\n");
}
// 右括号:计算直到左括号
else if (ch == ')') {
printf(" 遇到 ')',计算括号内表达式\n");
while (!isEmpty(&ops) && peek(&ops) != '(') {
double b = pop(&values);
double a = pop(&values);
char op = pop(&ops);
double result = applyOperator(a, b, op);
push(&values, result);
printf(" 计算: %.2f %c %.2f = %.2f\n", a, op, b, result);
}
pop(&ops); // 弹出 '('
}
// 运算符
else if (ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '^') {
printf(" 遇到运算符 '%c'\n", ch);
// 处理优先级:弹出优先级更高或相等的运算符
while (!isEmpty(&ops) && precedence(peek(&ops)) >= precedence(ch)) {
double b = pop(&values);
double a = pop(&values);
char op = pop(&ops);
double result = applyOperator(a, b, op);
push(&values, result);
printf(" 计算: %.2f %c %.2f = %.2f (优先级处理)\n",
a, op, b, result);
}
push(&ops, ch);
}
}
// 计算剩余的表达式
printf("\n 计算剩余表达式...\n");
while (!isEmpty(&ops)) {
double b = pop(&values);
double a = pop(&values);
char op = pop(&ops);
double result = applyOperator(a, b, op);
push(&values, result);
printf(" 计算: %.2f %c %.2f = %.2f\n", a, op, b, result);
}
double finalResult = pop(&values);
printf("\n最终结果: %.2f\n", finalResult);
return finalResult;
}
// 中缀转后缀(逆波兰表达式)
void infixToPostfix(char* infix, char* postfix) {
Stack s;
initStack(&s);
int j = 0;
printf("\n中缀转后缀表达式:\n");
printf("中缀: %s\n", infix);
for (int i = 0; infix[i] != '\0'; i++) {
char ch = infix[i];
if (ch == ' ') continue;
// 操作数:直接输出
if (isdigit(ch) || ch == '.') {
while (isdigit(infix[i]) || infix[i] == '.') {
postfix[j++] = infix[i++];
}
postfix[j++] = ' ';
i--;
}
// 左括号:压栈
else if (ch == '(') {
push(&s, ch);
}
// 右括号:弹栈直到左括号
else if (ch == ')') {
while (!isEmpty(&s) && peek(&s) != '(') {
postfix[j++] = pop(&s);
postfix[j++] = ' ';
}
pop(&s); // 弹出 '('
}
// 运算符
else {
while (!isEmpty(&s) && precedence(peek(&s)) >= precedence(ch)) {
postfix[j++] = pop(&s);
postfix[j++] = ' ';
}
push(&s, ch);
}
}
// 弹出剩余运算符
while (!isEmpty(&s)) {
postfix[j++] = pop(&s);
postfix[j++] = ' ';
}
postfix[j] = '\0';
printf("后缀: %s\n", postfix);
}
// 后缀表达式求值
double evaluatePostfix(char* postfix) {
Stack s;
initStack(&s);
printf("\n后缀表达式求值:\n");
for (int i = 0; postfix[i] != '\0'; i++) {
if (postfix[i] == ' ') continue;
// 数字:压栈
if (isdigit(postfix[i])) {
double num = 0;
while (isdigit(postfix[i])) {
num = num * 10 + (postfix[i] - '0');
i++;
}
i--; // 回退
push(&s, num);
printf(" 数字 %.2f 入栈\n", num);
}
// 运算符:弹出两个操作数,计算后压栈
else {
double b = pop(&s);
double a = pop(&s);
char op = postfix[i];
double result = applyOperator(a, b, op);
push(&s, result);
printf(" 计算: %.2f %c %.2f = %.2f\n", a, op, b, result);
}
}
double result = pop(&s);
printf(" 结果: %.2f\n", result);
return result;
}
// 表达式求值演示
void expressionEvaluationDemo() {
printf("\n表达式求值演示\n");
printf("=================\n");
char* expressions[] = {
"3 + 4 * 5", // 23
"(3 + 4) * 5", // 35
"10 / (2 + 3) * 4", // 8
"2 ^ 3 + 4 * 5", // 28
"1 + 2 * (3 + 4) / 5" // 3.8
};
for (int i = 0; i < 5; i++) {
printf("\n示例 %d:\n", i + 1);
printf("表达式: %s\n", expressions[i]);
char postfix[100];
infixToPostfix(expressions[i], postfix);
evaluatePostfix(postfix);
printf("---\n");
}
// 交互式计算器
printf("\n🖩 交互式计算器(输入'q'退出)\n");
printf("支持: + - * / ^ ( ) 和数字\n");
char input[100];
while (1) {
printf("\n请输入表达式: ");
fgets(input, sizeof(input), stdin);
// 移除换行符
input[strcspn(input, "\n")] = 0;
if (strcmp(input, "q") == 0) break;
evaluateExpression(input);
}
}
计算过程示例
计算
3 + 4 × 5:
步骤 字符 值栈 操作符栈 动作 1 3 [3] [] 数字入栈 2 + [3] [+] 运算符入栈 3 4 [3,4] [+] 数字入栈 4 × [3,4] [+,×] 优先级高于+,入栈 5 5 [3,4,5] [+,×] 数字入栈 结束 计算4×5=20 → [3,20] [+] 计算3+20=23 → [23] 结果:23
为什么需要两个栈?
值栈:存储操作数
运算符栈:存储运算符,处理优先级和括号
总结
这五个问题看似不同,但都体现了栈的核心能力:管理层次结构 和处理嵌套关系。
| 问题 | 栈的作用 | 核心思想 |
|---|---|---|
| 数制转换 | 反转顺序 | 后进先出 |
| 括号匹配 | 检查嵌套 | 对称匹配 |
| 行编辑 | 撤销操作 | 状态回退 |
| 迷宫求解 | 路径回溯 | 深度优先 |
| 表达式求值 | 优先级 |