Linux环境下的C语言编程(三十六)

五、栈的应用场景(接三十五)

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个元素

九、建议

  1. 选择合适的实现

    • 如果知道最大大小:用数组栈

    • 如果大小变化大:用链表栈或动态数组栈

    • 如果性能关键:用数组栈(缓存友好)

  2. 错误处理

    • 总是检查栈空时pop/peek

    • 检查栈满时push(数组栈)

    • 释放动态分配的内存

  3. 调试技巧

    复制代码
    // 添加调试信息
    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);
        }
    }

十、总结

栈是一种简单但强大的数据结构,它的后进先出特性使其在以下场景中不可替代:

  1. 函数调用管理:程序的执行依赖于调用栈

  2. 表达式求值:编译器和计算器的基础

  3. 括号匹配:语法检查的核心

  4. 回溯算法:深度优先搜索、迷宫求解

  5. 撤销操作:编辑器和浏览器的核心功能

相关推荐
踢球的打工仔44 分钟前
前端html(1)
前端·算法·html
MicroTech20251 小时前
MLGO微算法科技发布基于RANSAC-ISS-3DSC改进ICP的激光扫描仪点云快速配准算法
科技·算法·3d
_OP_CHEN1 小时前
【算法基础篇】(二十六)数据结构封神!Trie 树从入门到爆杀算法题:拼音输入法、单词统计都靠它
数据结构·c++·算法·蓝桥杯·trie树·算法竞赛·acm/icpc
小嘟嘟131 小时前
第2章 Shell 变量与参数传递:3 种定义方式 + 避坑指南
linux·运维·shell
rit84324991 小时前
LTE系统资源分配MATLAB实现示例(基于OFDMA的动态调度)
开发语言·matlab
代码游侠1 小时前
数据结构--队列
数据结构·笔记·学习·算法·链表
weixin_521431121 小时前
数据结构:树
数据结构
chilavert3181 小时前
技术演进中的开发沉思-231 Ajax:页面内容修改
开发语言·前端·javascript
李日灐1 小时前
C++STL:熟悉vector的底层实现,部分源码解析,迭代器失效和深层次浅拷贝
开发语言·c++