8、链式栈的实现

好,咱们今天来聊聊链式栈。说实话,顺序栈和链式栈就像一对兄弟,各有各的脾气。我个人习惯在项目里根据场景选,但不管怎么选,先把链式栈搞明白再说。

8.1 链式栈的节点定义

链式栈,说白了就是用链表实现栈。每个节点里存数据,再加一个指针指向下一个节点。嗯,这里要注意:栈顶在链表头部,这样入栈出栈都是 O(1)。

// 链式栈节点定义
typedef struct StackNode {
    int data;               // 数据域
    struct StackNode *next; // 指针域
} StackNode;

// 链式栈结构体
typedef struct {
    StackNode *top;  // 栈顶指针
    int size;        // 栈中元素个数
} LinkedStack;

我在项目中遇到过一个问题:有人把栈顶放在链表尾部,结果每次出栈都要遍历到末尾,性能直接崩了。你想想看,栈的操作都在一端,干嘛不选头部?

核心要点:链式栈的 top 指针永远指向链表第一个节点。入栈就是头插法,出栈就是删除头节点。

8.2 链式栈的入栈与出栈

入栈操作(push)

入栈其实很简单。新节点指向当前栈顶,然后更新栈顶指针。我刚开始写的时候老忘记更新 size,后来养成了习惯——每次操作完顺手维护一下计数器。

// 入栈
int push(LinkedStack *stack, int value) {
    if (stack == NULL) return -1;
    
    StackNode *newNode = (StackNode *)malloc(sizeof(StackNode));
    if (newNode == NULL) {
        printf("内存分配失败\n");
        return -1;
    }
    
    newNode->data = value;
    newNode->next = stack->top;  // 新节点指向当前栈顶
    stack->top = newNode;        // 更新栈顶
    stack->size++;
    
    return 0;
}

小技巧:入栈前一定要检查 malloc 返回值。我曾经在一个嵌入式项目里没检查,结果内存耗尽时程序直接跑飞,查了两天才找到原因。

出栈操作(pop)

出栈就是把栈顶节点摘下来。注意要保存数据,释放内存。嗯,这里有个坑——栈为空时不能出栈。

// 出栈
int pop(LinkedStack *stack, int *value) {
    if (stack == NULL || stack->top == NULL) {
        printf("栈为空,无法出栈\n");
        return -1;
    }
    
    StackNode *temp = stack->top;
    *value = temp->data;
    stack->top = temp->next;  // 栈顶下移
    free(temp);                // 释放内存
    stack->size--;
    
    return 0;
}

避坑指南:出栈后一定要 free 节点,否则内存泄漏。我在一个长期运行的服务里吃过亏——出栈不释放,跑了三天内存爆了。

8.3 链式栈的完整实现

把上面这些拼起来,再加个初始化、判空、获取栈顶,就是一个完整的链式栈了。

// 初始化
void initStack(LinkedStack *stack) {
    stack->top = NULL;
    stack->size = 0;
}

// 判空
int isEmpty(LinkedStack *stack) {
    return stack->top == NULL;
}

// 获取栈顶元素
int getTop(LinkedStack *stack, int *value) {
    if (isEmpty(stack)) return -1;
    *value = stack->top->data;
    return 0;
}

// 销毁栈
void destroyStack(LinkedStack *stack) {
    while (stack->top != NULL) {
        StackNode *temp = stack->top;
        stack->top = temp->next;
        free(temp);
    }
    stack->size = 0;
}

8.4 顺序栈与链式栈的对比

说到对比,我直接给你一张表,一目了然。

对比维度 顺序栈 链式栈
存储方式 连续内存(数组) 离散内存(链表节点)
空间效率 固定大小,可能浪费 动态分配,按需使用
时间效率 入栈出栈 O(1),无额外开销 入栈出栈 O(1),但有 malloc/free 开销
扩容 需要 realloc,可能搬移数据 无需扩容,随用随分配
内存碎片 无碎片 频繁分配释放会产生碎片
适用场景 数据量可预估,追求极致性能 数据量不确定,需要灵活扩展

为什么会这样?说白了就是顺序栈用数组,链式栈用链表。数组访问快但扩容麻烦,链表灵活但每次操作都要动内存。

我的建议:在嵌入式环境里,如果栈的最大深度能确定,优先用顺序栈。如果数据量变化大,或者栈需要频繁创建销毁,那就用链式栈。我在一个传感器数据采集项目里,因为数据量波动很大,最后选了链式栈,省了不少心。

8.5 知识体系图

下面这张图把链式栈的核心逻辑串起来了,你一看就明白。

链式栈知识体系 链式栈 节点定义:data + next 核心操作:入栈(push) 出栈(pop) 入栈:头插法 出栈:删除头节点 对比:顺序栈 vs 链式栈

你看,链式栈的核心就三块:节点怎么定义、入栈出栈怎么实现、跟顺序栈比有什么优劣。搞清楚了这些,面试写代码基本没问题。

最后说一句:链式栈的代码量比顺序栈多,但灵活性也高。我建议你两种都手写一遍,感受一下区别。写多了自然就知道什么时候该用哪个了。


公众号:蓝海资料掘金营,微信deep3321