考点频率 :★★★★★(数据结构必考,选择题常与顺序表对比考查)
难度 :⭐⭐⭐
建议:重点理解单链表的节点结构、头指针与头节点的区别,掌握插入/删除操作的时间复杂度
1️⃣ 为什么需要链式存储?
顺序表(数组)虽然支持随机访问,但插入和删除需要移动大量元素,效率很低。如果我们需要频繁进行插入和删除操作,顺序表就不太合适了。
链式存储 应运而生。它的核心思想是:用"指针"把零散的内存块串联起来。逻辑上相邻的元素,物理上不需要相邻,每个元素都"记住"下一个元素的地址。
打个比方 :顺序表就像电影院的一排连座 ,座位号连续,找人快,但有人中途插进来就麻烦(要挪动整排的人)。链表就像寻宝游戏------每个线索纸条上写着下一个线索藏在哪里(地址),你按地址去找,不用管它们是不是在同一个抽屉里。插入新线索时,只需要改两张纸条上的地址就行了。
2️⃣ 单链表的节点结构
单链表的每个节点由两部分组成:
| 组成部分 | 含义 | 作用 |
|---|---|---|
| 数据域(data) | 存储数据元素本身 | 存放实际的数据 |
| 指针域(next) | 存储下一个节点的地址 | 指向后继节点,把零散的节点串起来 |
c
// 单链表节点的结构定义(C语言风格伪代码)
typedef struct Node {
int data; // 数据域
struct Node* next; // 指针域,指向下一个节点
} Node;
3️⃣ 头指针与头节点(关键概念)
这是软考中一个容易混淆的概念,务必分清:
| 概念 | 定义 | 作用 |
|---|---|---|
| 头指针(Head Pointer) | 指向链表中第一个节点的指针 | 是链表的唯一入口,通过它才能找到整个链表 |
| 头节点(Head Node) | 在第一个数据节点之前额外添加的一个辅助节点 | 简化插入/删除操作的代码逻辑,避免处理空表等特殊情况 |
带头节点的单链表结构:
头指针 → 头节点(数据域为空或存表长) → 第1个数据节点 → 第2个数据节点 → ... → NULL
不带头节点的单链表结构:
头指针 → 第1个数据节点 → 第2个数据节点 → ... → NULL
软考考点 :在带头节点的单链表中,头指针永远不为空 (即使链表为空,头节点也存在);在不带头节点的单链表中,空链表的头指针为 NULL。
4️⃣ 单链表的基本操作与时间复杂度
4.1 查找操作(按值查找 / 按位查找)
按值查找:
- 从第一个数据节点开始,依次比较每个节点的数据域,直到找到目标或遍历结束。
- 时间复杂度 :O(n)O(n)O(n)(平均需遍历一半节点)
按位查找(获取第 iii 个节点):
- 从头节点开始,沿着
next指针逐个向后移动 iii 次。 - 时间复杂度 :O(n)O(n)O(n)(不支持随机访问,必须从头遍历)
这是链表和数组最显著的区别:链表不支持随机访问,查找必须从头开始。
4.2 插入操作(在第 iii 个位置插入新节点)
以带头节点的单链表 为例,在第 iii 个位置插入新节点:
- 找到第 i−1i-1i−1 个节点(即插入位置的前驱节点)ppp(从头节点开始遍历)
- 将新节点 sss 的
next指向 ppp 的next - 将 ppp 的
next指向 sss
c
// 在第 i 个位置插入新节点(带头节点)
s->next = p->next; // 先连后面
p->next = s; // 再连前面
时间复杂度分析:
- 查找前驱节点:O(n)O(n)O(n)(需遍历)
- 插入操作本身(修改指针):O(1)O(1)O(1)
- 总体时间复杂度 :O(n)O(n)O(n)(主要开销在查找)
特别注意 :如果已经知道插入位置的前驱节点,插入操作本身是 O(1)O(1)O(1)。这是链表相对于顺序表的一大优势------插入不需要移动元素。
4.3 删除操作(删除第 iii 个位置的节点)
以带头节点的单链表 为例,删除第 iii 个位置的节点:
- 找到第 i−1i-1i−1 个节点(即待删除节点的前驱节点)ppp
- 用指针 qqq 指向待删除节点:q=p−>nextq = p->nextq=p−>next
- 跳过待删除节点:p−>next=q−>nextp->next = q->nextp−>next=q−>next
- 释放 qqq 的内存
c
// 删除第 i 个节点(带头节点)
q = p->next; // q 指向待删除节点
p->next = q->next; // 跳过 q
free(q); // 释放 q 的内存
时间复杂度分析:
- 查找前驱节点:O(n)O(n)O(n)
- 删除操作本身(修改指针):O(1)O(1)O(1)
- 总体时间复杂度 :O(n)O(n)O(n)(主要开销在查找)
5️⃣ 顺序表 vs 单链表(完整对比)
| 对比维度 | 顺序表(数组) | 单链表 |
|---|---|---|
| 存储方式 | 连续内存 | 零散内存(指针链接) |
| 随机访问 | ✅ O(1)O(1)O(1) | ❌ O(n)O(n)O(n)(必须从头遍历) |
| 按值查找 | O(n)O(n)O(n)(无序) | O(n)O(n)O(n) |
| 插入(已知位置) | ❌ O(n)O(n)O(n)(需移动元素) | ✅ O(1)O(1)O(1)(仅修改指针) |
| 插入(未知位置) | O(n)O(n)O(n) | O(n)O(n)O(n)(需先查找) |
| 删除(已知位置) | ❌ O(n)O(n)O(n)(需移动元素) | ✅ O(1)O(1)O(1)(仅修改指针) |
| 删除(未知位置) | O(n)O(n)O(n) | O(n)O(n)O(n)(需先查找) |
| 存储密度 | 高(1.0,无额外开销) | 低(每个节点多存一个指针) |
| 适用场景 | 查找多、增删少 | 增删多、查找少 |
6️⃣ 经典例题
例题1:在带头节点的单链表中,插入一个新节点时,需要修改的指针数量是( )。
A. 0 个
B. 1 个
C. 2 个
D. 3 个
解析 :插入操作需要将前驱节点的 next 指向新节点,同时将新节点的 next 指向后继节点,共修改 2 个指针 。选 C。
例题2 :在长度为 nnn 的带头节点的单链表中,删除第 iii(1≤i≤n1 \le i \le n1≤i≤n)个节点,需要遍历的节点个数是( )。
A. iii
B. i−1i-1i−1
C. i+1i+1i+1
D. n−in-in−i
解析 :删除第 iii 个节点需要找到第 i−1i-1i−1 个节点(前驱节点),而从带头节点的链表头开始,需要遍历 iii 个节点才能到达第 i−1i-1i−1 个节点?实际上带头节点中,头节点是第0个,第1个节点是第一个数据节点。找到第 i−1i-1i−1 个数据节点需要从 head 开始移动 i−1i-1i−1 次。更准确地说,要找到第 i−1i-1i−1 个数据节点,需要从头节点开始经过 iii 个节点?不,头节点是第0个,它的 next 指向第1个数据节点。要找第 i−1i-1i−1 个数据节点,需要从头节点开始走 i−1i-1i−1 步。但软考更常见的考法是:删除第 iii 个节点需要找到它的前驱,需定位到第 i−1i-1i−1 个节点,从带头节点的链表头(头节点)开始需要移动 i−1i-1i−1 次。若选项中有 i−1i-1i−1,选它。
例题3(概念判断):单链表支持随机访问,因此可以像数组一样通过下标直接获取任意位置的元素。( )
解析 :错误。单链表不支持随机访问 ,只能从头开始顺序遍历,时间复杂度为 O(n)O(n)O(n)。
7️⃣ 记忆口诀
节点分两段,数据和指针。
头指针定入口,头节点是辅助。
插入删除改指针,不用搬家是优势。
随机访问不支持,查找必须从头走。
8️⃣ 小测验(评论区对答案)
在带头节点的单链表中,若要在第 iii 个节点之前插入一个新节点(i≥1i \ge 1i≥1),需要先找到( )。
A. 第 iii 个节点
B. 第 i−1i-1i−1 个节点
C. 第 i+1i+1i+1 个节点
D. 头节点
🔔 本专栏日更,点击头像 → 专栏《软考中级高频考点》订阅,第一时间接收新内容
#软考中级 #软件设计师 #单链表 #链式存储 #线性表 #数据结构 #软考备考