数据结构入门:链表超全解析
链表是线性表最常用的实现之一,完美解决了顺序表插入删除效率低、扩容浪费空间的问题。本文把课程里所有链表知识点完整整理
一、链表基本概念
链表是一种物理存储结构上非连续、非顺序 的存储结构,数据元素的逻辑顺序 通过链表中的指针链接次序实现。
核心特点:
- 逻辑上连续,物理上不一定连续
- 结点一般从堆上申请
- 两次申请的空间可能连续,也可能不连续
二、链表的分类(8种组合)
链表按3个维度组合,一共 8 种结构:
- 单向 / 双向
- 带头 / 不带头
- 循环 / 非循环
实际最常用两种
-
无头单向非循环链表
- 结构简单
- 一般不单独存数据,常作为哈希桶、图邻接表等子结构
- 笔试面试高频
-
带头双向循环链表
- 结构最复杂
- 实际工程单独存数据几乎都用它
- 实现反而更简单、操作更高效
三、无头单向非循环链表实现
1. 结点结构
c
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
2. 核心接口声明
c
// 动态申请一个结点
SListNode* BuySListNode(SLTDateType x);
// 打印链表
void SListPrint(SListNode* plist);
// 尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 尾删
void SListPopBack(SListNode** pplist);
// 头删
void SListPopFront(SListNode** pplist);
// 查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 在pos之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 删除pos之后的值
void SListEraseAfter(SListNode* pos);
四、带头双向循环链表实现
1. 结点结构
c
typedef int LTDataType;
typedef struct ListNode
{
LTDataType _data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
2. 核心接口声明
c
// 创建头结点
ListNode* ListCreate();
// 销毁
void ListDestory(ListNode* plist);
// 打印
void ListPrint(ListNode* plist);
// 尾插/尾删
void ListPushBack(ListNode* plist, LTDataType x);
void ListPopBack(ListNode* plist);
// 头插/头删
void ListPushFront(ListNode* plist, LTDataType x);
void ListPopFront(ListNode* plist);
// 查找
ListNode* ListFind(ListNode* plist, LTDataType x);
// 在pos前插入
void ListInsert(ListNode* pos, LTDataType x);
// 删除pos位置结点
void ListErase(ListNode* pos);
五、链表面试题(课程完整版)
- 删除链表中等于给定值 val 的所有结点
- 反转一个单链表
- 返回链表中间结点(偶数返回第二个)
- 输出倒数第 k 个结点
- 合并两个有序链表
- 以 x 为基准分割链表(小在前,大等于在后)
- 判断链表回文结构
- 找两个链表的第一个公共结点
- 判断链表是否有环
- 找到链表入环第一个结点
- 带随机指针的链表深度拷贝
六、链表环问题
1. 判断是否有环:快慢指针
- 慢指针一次走 1 步
- 快指针一次走 2 步
- 有环一定会相遇;无环快指针先到 NULL
2. 为什么快指针只能走 2 步?
- 快 3 步、慢 1 步:可能永远不相遇(刚好套圈错过)
- 快 2 步、慢 1 步:一定相遇
- 环最小长度为 1,即使套圈也会在同一点
3. 找环的入口结点(结论)
- 一个指针从链表起点出发
- 一个指针从相遇点出发
- 均一次走 1 步,最终在环入口相遇
公式推导(课程版)
设:
- H:起点
- E:环入口
- M:相遇点
- L:H→E 长度
- X:E→M 长度
- R:环长
快慢指针路程:
- fast:L + X + n×R
- slow:L + X
- 2×slow = fast
→ L + X = n×R
→ L = n×R − X
即:起点走 L 步 = 相遇点绕环走到入口。
七、顺序表 vs 链表(核心对比)
| 不同点 | 顺序表 | 链表 |
|---|---|---|
| 存储空间 | 物理一定连续 | 逻辑连续,物理不一定连续 |
| 随机访问 | 支持 O(1) | 不支持 O(N) |
| 插入/删除 | 需搬移元素 O(N) | 只改指针,高效 |
| 容量 | 需扩容 | 无容量限制 |
| 缓存利用率 | 高 | 低 |
| 适用场景 | 频繁访问、少修改 | 频繁插入删除 |
总结
- 链表物理不连续、靠指针相连,解决顺序表缺陷
- 单向链表简单适合面试;双向循环链表适合工程使用
- 快慢指针是链表环问题、中间节点、倒数节点的万能解法
- 按需选择:频繁读取用顺序表,频繁增删用链表