前言
栈(Stack) 是一种后进先出(LIFO)的线性数据结构。** ****前面我们学习了顺序栈** (数组实现),今天我们学习它的兄弟 ------链栈(链式栈)。
链栈 = 用单链表实现的栈 它完美解决了顺序栈容量固定、需要扩容的缺点。
一、链栈是什么?
链栈 = 采用链式存储结构实现的栈 本质就是一个只能在表头进行插入、删除操作的单链表。
特点:
不需要预先分配空间,想用多少申请多少
不存在栈满溢出问题(除非内存耗尽)
不用扩容
入栈、出栈都在表头完成,效率 O (1)
每个节点包含:数据域 + 指针域
二、链栈结构体设计(核心)
链栈采用 "头结点辅助管理" 模式,结构非常清晰:
cpp
#pragma once
typedef char ELEM_TYPE;
// 链栈节点结构体
typedef struct Stack_Node
{
ELEM_TYPE data; // 数据域
struct Stack_Node* next;// 指针域
}Stack_Node, *PStack_Node;
// 链栈辅助结构体(管理栈顶 + 长度)
typedef struct
{
PStack_Node top; // 栈顶指针
int count; // 节点个数(有效长度)
}LinkStack;
结构说明
-
Stack_Node:真正存储数据的节点
-
LinkStack:辅助管理结构
-
top:永远指向栈顶节点 -
count:记录栈中元素个数,获取长度 O (1)
-
三、链栈支持的操作(全覆盖)
cpp
// 1. 初始化
void Init_Stack(LinkStack* ls);
// 2. 入栈(头插)
bool Push(LinkStack* ls, ELEM_TYPE val);
// 3. 出栈(头删)
bool Pop(LinkStack* ls);
// 4. 获取栈顶元素
ELEM_TYPE Top(LinkStack* ls);
// 5. 判空
bool Is_Empty(LinkStack* ls);
// 6. 获取有效长度
int Get_Length(LinkStack* ls);
// 7. 清空
void Clear(LinkStack* ls);
// 8. 销毁(释放内存)
void Destroy(LinkStack* ls);
// 9. 打印
void Show(LinkStack* ls);
四、核心函数逐行精讲(最关键)
1. 初始化
cpp
void Init_Stack(LinkStack* ls)
{
assert(ls != NULL);
ls->count = 0; // 元素个数为0
ls->top = NULL; // 栈顶为空
}
作用:栈一开始是空的,栈顶指针为 NULL,长度为 0。
2. 入栈 Push(核心:头插法)
链栈入栈 = 单链表头插 时间复杂度 O(1)
cpp
bool Push(LinkStack* ls, ELEM_TYPE val)
{
assert(ls != NULL);
// 申请新节点
Stack_Node* p = (Stack_Node*)malloc(sizeof(Stack_Node));
if (p == NULL)
return false;
p->data = val;
p->next = ls->top; // 新节点指向原来栈顶
ls->top = p; // 更新栈顶
ls->count++; // 长度+1
return true;
}
3. 出栈 Pop(核心:头删法)
链栈出栈 = 删除链表第一个节点 时间复杂度 O(1)
cpp
bool Pop(LinkStack* ls)
{
assert(ls != NULL);
if (Is_Empty(ls))
return false;
Stack_Node* p = ls->top;
ls->top = p->next; // 栈顶后移
free(p); // 释放旧栈顶
ls->count--; // 长度-1
return true;
}
4. 获取栈顶元素 Top
cpp
ELEM_TYPE Top(LinkStack* ls)
{
assert(ls != NULL);
if (Is_Empty(ls))
exit(1);
return ls->top->data;
}
5. 判空
cpp
bool Is_Empty(LinkStack* ls)
{
assert(ls != NULL);
//return ls->top == NULL;//2选1
return ls->count == 0;
}
6. 获取长度
cpp
int Get_Length(LinkStack* ls)
{
assert(ls != NULL);
return ls->count; // O(1) 直接返回,不用遍历!
}
用 count 记录长度,不用遍历链表,效率极高!
7. 销毁栈(重要!防止内存泄漏)
cpp
void Destroy(LinkStack* ls)
{
PStack_Node p = ls->top;
PStack_Node q = NULL;
while (p != NULL)
{
q = p->next;
free(p);
p = q;
}
ls->count = 0;
ls->top = NULL;
}
8. 清空
cpp
void Clear(LinkStack* ls)
{
Destroy(ls);
}
9. 打印栈
cpp
void Show(LinkStack* ls)
{
Stack_Node* p = ls->top;
for (; p != NULL; p = p->next)
{
printf("%c ", p->data);
}
printf("\n");
}
五、测试主函数(可直接运行)
cpp
int main()
{
LinkStack head;
Init_Stack(&head);
Push(&head, 'A');
Push(&head, 'B');
Push(&head, 'C');
Show(&head); // 输出:C B A
Pop(&head);
Show(&head); // 输出:B A
printf("%c \n", Top(&head)); // 输出:B
Destroy(&head);
return 0;
}
运行结果

六、链栈 VS 顺序栈
| 特性 | 顺序栈 | 链栈 |
|---|---|---|
| 存储结构 | 连续数组 | 链式节点 |
| 容量 | 固定 / 需扩容 | 无限(不溢出) |
| 入栈出栈 | O(1) | O(1) |
| 内存分配 | 静态 / 动态 | 动态 |
| 访问速度 | 快(缓存友好) | 较慢 |
| 适用场景 | 已知最大容量 | 无法预估容量 |
七、高频考点(必背)
-
链栈为什么用头插法? 因为栈只能在栈顶操作,头插 = 栈顶入栈,时间复杂度 O (1)。
-
链栈有没有栈满? 一般认为没有,只有内存不足时才会申请失败。
-
链栈的栈顶在链表头还是尾? 链表头部。
-
链栈如何获取长度? 用
count变量记录,O(1) 返回,不用遍历。 -
链栈和顺序栈最大区别? 顺序栈需要扩容,链栈不需要扩容。