Skip to content

链栈

为什么需要链栈

顺序栈使用静态数组实现,需要预先分配固定大小的存储空间。当栈的使用量难以预估时,分配过大浪费空间,分配过小则会栈满溢出。

链栈使用链表来实现栈,每次入栈动态申请一个结点,出栈时释放结点,天然不存在栈满上溢的问题。408 考研中,链栈通常作为顺序栈的对比考点出现,需要掌握其基本操作和优缺点。

核心思想

链栈的核心特点:

  • 以链表头部作为栈顶:入栈和出栈都在链表头部进行,时间复杂度 O(1)
  • 无需预分配空间:每个结点动态分配,不会栈满溢出(除非内存耗尽)
  • 不支持随机访问:访问栈中第 i 个元素需要从栈顶依次遍历

结点结构定义:

cpp
typedef struct LinkNode {
    int data;             // 数据域
    struct LinkNode *next; // 指针域,指向下一个结点
} LinkNode, *LinkStack;

链栈的逻辑结构:

栈顶 → [a₃|next] → [a₂|next] → [a₁|NULL]

栈顶指针 top 始终指向链表的第一个结点(即栈顶元素)。

交互可视化

通过下方的交互动画,你可以逐步观察链栈的执行过程:

加载可视化中...

操作详解

入栈操作

入栈即在链表头部插入(头插法)一个新结点。

关键步骤:

  1. 创建新结点,赋值数据域
  2. 新结点的 next 指向当前栈顶
  3. 更新栈顶指针指向新结点
cpp
// 入栈(不带头结点)
bool Push(LinkStack &top, int x) {
    LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
    if (s == NULL) return false; // 内存分配失败
    s->data = x;
    s->next = top;
    top = s;
    return true;
}

链栈入栈不需要判断栈满,只要内存足够就能继续入栈。

出栈操作

出栈即删除链表头结点,并取出其数据。

关键步骤:

  1. 判断栈是否为空(top == NULL
  2. 取出栈顶元素的值
  3. 栈顶指针后移一位
  4. 释放原栈顶结点
cpp
// 出栈(不带头结点)
bool Pop(LinkStack &top, int &x) {
    if (top == NULL) return false; // 栈空
    LinkNode *p = top;
    x = p->data;
    top = p->next;
    free(p);
    return true;
}

复杂度分析

操作时间复杂度说明
入栈O(1)头插法,无需移动元素
出栈O(1)删除头结点,无需移动元素
读取栈顶O(1)直接访问 top 指向的结点
判空O(1)判断 top 是否为 NULL

空间复杂度:每个元素需要额外存储一个指针域,存储密度低于顺序栈。

链栈 vs 顺序栈对比

对比项顺序栈链栈
存储方式静态数组,连续空间链表,离散空间
栈满溢出可能溢出不会溢出(内存足够时)
空间利用可能浪费(预分配过大)按需分配,无浪费
存储密度高(无指针开销)低(每个结点多一个指针)
实现难度简单稍复杂
缓存性能好(连续存储)差(离散存储)

考研高频考点

  • ⭐ 链栈的入栈和出栈操作实现(头插法/删头结点)
  • ⭐ 链栈 vs 顺序栈的优缺点对比(简答题高频)
  • ⭐ 链栈不存在栈满溢出问题的原因(概念题)
  • 带头结点和不带头结点的链栈实现差异
  • 共享栈、顺序栈、链栈的适用场景选择