五、栈的应用场景(接三十五)
1. 函数调用栈
// 函数调用示例
void functionC() {
printf("在函数C中\n");
// 这里会保存返回地址到栈中
}
void functionB() {
printf("在函数B中\n");
functionC(); // 调用C,将返回地址压栈
printf("返回函数B\n");
}
void functionA() {
printf("在函数A中\n");
functionB(); // 调用B,将返回地址压栈
printf("返回函数A\n");
}
int main() {
functionA(); // 调用A,将返回地址压栈
return 0;
}
/* 执行过程栈的变化: 1. main调用functionA:main返回地址入栈 2. functionA调用functionB:A返回地址入栈 3. functionB调用functionC:B返回地址入栈 4. functionC返回:弹出B的返回地址 5. functionB返回:弹出A的返回地址 6. functionA返回:弹出main的返回地址 */
2. 表达式求值
// 使用栈计算后缀表达式(逆波兰表达式)
// 例如:3 4 + 5 × 6 - 等价于 (3+4)×5-6
#include <ctype.h>
int evaluatePostfix(char* expression) {
int stack[100];
int top = -1;
for (int i = 0; expression[i]; i++) {
// 如果是数字,压栈
if (isdigit(expression[i])) {
int num = 0;
while (isdigit(expression[i])) {
num = num * 10 + (expression[i] - '0');
i++;
}
i--; // 回退一个字符
stack[++top] = num;
}
// 如果是运算符,弹出两个操作数,计算后压栈
else if (expression[i] == '+' || expression[i] == '-' ||
expression[i] == '*' || expression[i] == '/') {
int b = stack[top--];
int a = stack[top--];
switch (expression[i]) {
case '+': stack[++top] = a + b; break;
case '-': stack[++top] = a - b; break;
case '*': stack[++top] = a * b; break;
case '/': stack[++top] = a / b; break;
}
}
}
return stack[top];
}
3. 括号匹配检查
// 检查表达式中的括号是否匹配
// 例如:{[( )]} 匹配,{[( ])} 不匹配
bool isBalanced(char* expression) {
char stack[100];
int top = -1;
for (int i = 0; expression[i]; i++) {
char ch = expression[i];
// 如果是左括号,压栈
if (ch == '(' || ch == '{' || ch == '[') {
stack[++top] = ch;
}
// 如果是右括号
else if (ch == ')' || ch == '}' || ch == ']') {
// 检查栈是否为空
if (top == -1) return false;
char topChar = stack[top--];
// 检查括号是否匹配
if ((ch == ')' && topChar != '(') ||
(ch == '}' && topChar != '{') ||
(ch == ']' && topChar != '[')) {
return false;
}
}
}
// 最后栈应该为空
return top == -1;
}
// 测试
void testParenthesisMatching() {
char* test1 = "{[(a+b)*(c-d)]}"; // 匹配
char* test2 = "{[()]"; // 不匹配
char* test3 = "{[(])}"; // 不匹配
printf("测试1: %s -> %s\n", test1, isBalanced(test1) ? "匹配" : "不匹配");
printf("测试2: %s -> %s\n", test2, isBalanced(test2) ? "匹配" : "不匹配");
printf("测试3: %s -> %s\n", test3, isBalanced(test3) ? "匹配" : "不匹配");
}
4. 浏览器的前进/后退功能
// 简化的浏览器历史管理
#include <string.h>
#define MAX_HISTORY 100
typedef struct {
char* urls[MAX_HISTORY];
char* forwardStack[MAX_HISTORY];
char* backwardStack[MAX_HISTORY];
int forwardTop;
int backwardTop;
} Browser;
// 访问新页面
void visitPage(Browser* browser, char* url) {
// 清空前进栈
browser->forwardTop = -1;
// 如果当前页面存在,压入后退栈
if (browser->urls[0] != NULL) {
browser->backwardStack[++browser->backwardTop] = browser->urls[0];
}
// 设置新页面为当前页面
browser->urls[0] = strdup(url);
printf("访问: %s\n", url);
}
// 后退
void goBack(Browser* browser) {
if (browser->backwardTop == -1) {
printf("无法后退\n");
return;
}
// 当前页面压入前进栈
browser->forwardStack[++browser->forwardTop] = browser->urls[0];
// 后退栈顶页面成为当前页面
browser->urls[0] = browser->backwardStack[browser->backwardTop--];
printf("后退到: %s\n", browser->urls[0]);
}
// 前进
void goForward(Browser* browser) {
if (browser->forwardTop == -1) {
printf("无法前进\n");
return;
}
// 当前页面压入后退栈
browser->backwardStack[++browser->backwardTop] = browser->urls[0];
// 前进栈顶页面成为当前页面
browser->urls[0] = browser->forwardStack[browser->forwardTop--];
printf("前进到: %s\n", browser->urls[0]);
}
5. 深度优先搜索(DFS)
/
/ 使用栈实现图的深度优先搜索
void dfs(int graph[][5], int start, int n) {
int visited[5] = {0};
int stack[100];
int top = -1;
// 起始节点入栈
stack[++top] = start;
visited[start] = 1;
printf("DFS遍历: ");
while (top != -1) {
// 弹出栈顶节点
int node = stack[top--];
printf("%d ", node);
// 将未访问的邻居节点入栈
for (int i = n-1; i >= 0; i--) { // 逆序入栈保证顺序正确
if (graph[node][i] == 1 && !visited[i]) {
stack[++top] = i;
visited[i] = 1;
}
}
}
printf("\n");
}
六、栈的实现方式对比
| 实现方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 数组栈 | 实现简单,访问速度快 | 大小固定,可能溢出 | 大小已知且不变的情况 |
| 链表栈 | 动态大小,无需预分配 | 内存开销大,访问稍慢 | 大小不确定,频繁push/pop |
| 动态数组栈 | 动态扩容,访问快 | 扩容时可能复制数据 | 需要动态大小且性能重要 |
七、栈的常见题
1. 最小栈问题
设计一个栈,支持push、pop、top操作,并能在常数时间内检索到最小元素。
// 使用两个栈实现
typedef struct {
int* mainStack; // 主栈
int* minStack; // 最小栈
int top;
int capacity;
} MinStack;
void minStackPush(MinStack* obj, int val) {
// 压入主栈
obj->mainStack[++obj->top] = val;
// 如果是最小值或第一个元素,压入最小栈
if (obj->top == 0 || val <= obj->minStack[obj->top-1]) {
obj->minStack[obj->top] = val;
} else {
obj->minStack[obj->top] = obj->minStack[obj->top-1];
}
}
int minStackGetMin(MinStack* obj) {
return obj->minStack[obj->top];
}
2. 用栈实现队列
typedef struct {
int pushStack[100]; // 用于入队
int popStack[100]; // 用于出队
int pushTop;
int popTop;
} MyQueue;
// 入队:直接压入push栈
void myQueuePush(MyQueue* obj, int x) {
obj->pushStack[++obj->pushTop] = x;
}
// 出队:如果pop栈为空,将push栈所有元素转移到pop栈
int myQueuePop(MyQueue* obj) {
if (obj->popTop == -1) {
while (obj->pushTop != -1) {
obj->popStack[++obj->popTop] = obj->pushStack[obj->pushTop--];
}
}
return obj->popStack[obj->popTop--];
}
八、栈的时间复杂度分析
| 操作 | 数组实现 | 链表实现 | 说明 |
| push() | O(1) | O(1) | 都是常数时间 |
| pop() | O(1) | O(1) | 都是常数时间 |
| peek() | O(1) | O(1) | 都是常数时间 |
| isEmpty() | O(1) | O(1) | 都是常数时间 |
| 空间复杂度 | O(n) | O(n) | 都需要存储n个元素 |
|---|
九、建议
-
选择合适的实现:
-
如果知道最大大小:用数组栈
-
如果大小变化大:用链表栈或动态数组栈
-
如果性能关键:用数组栈(缓存友好)
-
-
错误处理:
-
总是检查栈空时pop/peek
-
检查栈满时push(数组栈)
-
释放动态分配的内存
-
-
调试技巧:
// 添加调试信息 void debugPush(ArrayStack* stack, int value) { printf("[DEBUG] 压栈前: top=%d, ", stack->top); if (!push(stack, value)) { printf("压栈失败!\n"); } else { printf("压栈后: top=%d\n", stack->top); } }
十、总结
栈是一种简单但强大的数据结构,它的后进先出特性使其在以下场景中不可替代:
函数调用管理:程序的执行依赖于调用栈
表达式求值:编译器和计算器的基础
括号匹配:语法检查的核心
回溯算法:深度优先搜索、迷宫求解
撤销操作:编辑器和浏览器的核心功能