单链表
单链表是全章算法题的底座。 教材【命题追踪】列了单链表的应用 2009、2012、2013、2015、2016、2019 六道大题,外加「单链表插入操作的过程」2016、2024 两道选择题。选择题的考法是给一串指针语句问执行结果,逐句画图就不会错。
机制
2.3.1 单链表的定义
每个结点除存放元素自身的信息(data)外,还存放一个指向其后继的指针(next)。
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode, *LinkList;单链表不需要大量连续的存储单元,但附加的指针域浪费存储空间。元素离散分布,是非随机存取的存储结构:查找特定结点必须从表头开始依次遍历。
头指针与头结点:
- 头指针:标识一个单链表,始终指向链表的第一个结点,为
NULL时表示空表。 - 头结点:在第一个数据结点之前附加的结点,数据域可以不存信息,也可以记录表长。带头结点时,头指针指向头结点。
教材原话:「不管带不带头结点,头指针都始终指向链表的第一个结点,而头结点是带头结点的链表中的第一个结点。」引入头结点的两个优点:
- 第一个数据结点的位置存放在头结点的指针域中,在第一个位置上的操作和其他位置一致,无须特殊处理。
- 无论链表是否为空,头指针都是指向头结点的非空指针,空表和非空表的处理统一。
2.3.2 基本操作(默认带头结点)
| 操作 | 要点 | 时间 |
|---|---|---|
| 初始化 | 带头:创建头结点,L->next=NULL;不带头:L=NULL | |
| 求表长 | 从第一个结点起计数,不包括头结点;带不带头结点写法略有不同 | |
| 按序号查找 | p=L; j=0;(头结点是第 0 个结点),走到第 | |
| 按值查找 | 从 L->next 起比较 data | |
| 插入到第 | 找到第 *p,再后插 | 查找 |
| 删除第 | 找到第 *p,q=p->next; p->next=q->next; free(q); | 同上 |
| 头插法建表 | 新结点插在头结点之后;读入顺序与表中顺序相反,可用来逆置 | |
| 尾插法建表 | 设表尾指针 r,新结点接在 r 后;最后 r->next=NULL |
插入的两句顺序不能颠倒:
s->next=p->next; // ①
p->next=s; // ②若先执行 ②,*p 原后继的地址就丢了,再执行 ① 相当于 s->next=s。
不带头结点时,插入或删除位置 L 指向新的首结点。带头结点则不用。
两个 的技巧:前插与删除给定结点
单链表只能从前往后找,所以「在 *p 之前插入」和「删除 *p 本身」本来都要先花
| 操作 | 做法 | 代码 |
|---|---|---|
在 *p 前插入 *s | 先把 *s 后插到 *p 后面,再交换两者的 data | s->next=p->next; p->next=s; 然后交换 p->data 与 s->data |
删除给定结点 *p | 把后继的值赋给 *p,再删除后继 | q=p->next; p->data=q->data; p->next=q->next; free(q); |
删除的技巧要求 *p 有后继;*p 是尾结点时仍要从头找前驱。
手算模板
指针语句题:逐句画图,每句只改一个指针。
- 画出初始状态,标出每个指针变量(
h、p、q)指向哪个结点。 - 一次执行一句:
x->next=y改的是x所指结点的next域,x=y改的是指针变量本身。 - 执行完再从头指针走一遍,读出结果。
以 2024 为例,设 h 为头结点,链表为 p 指向
| 语句 | 效果 | 链表 |
|---|---|---|
q=p->next; | q 指向 | |
p->next=q->next; | ||
q->next=h->next; | ||
h->next=q; | 头结点指向 |
结果:将 q 所指结点移动到 L 的头结点之后(选 D)。题目给的 p 「非首且非尾」保证 q 存在,且 q 不是首结点。
边界
| 说法 | 判断 | 说明 |
|---|---|---|
| 「头指针指向头结点,所以头指针就是头结点」 | ❌ | 头指针是指针,头结点是结点;不带头结点时头指针指向第一个数据结点 |
| 「增加头结点是为了使单链表至少有一个结点」 | ❌ | 是为了方便运算的实现:统一首位置的操作、统一空表与非空表 |
「带头结点的单链表判空用 head==NULL」 | ❌ | 用 head->next==NULL;head==NULL 是不带头结点的判空 |
| 「单链表的表长包括头结点」 | ❌ | 不包括 |
| 「链式存储时,结点内的存储单元地址可以不连续」 | ❌ | 不同结点之间可以不连续,结点内必须连续 |
| 「链式存储不能反映数据之间的逻辑关系」 | ❌ | 用指针表示逻辑关系,能更方便地表示各种逻辑结构 |
| 「单链表插入的两句可以交换顺序」 | ❌ | 只知道 *p 时不能:先改 p->next 会丢掉原后继。q、p 两端都有指针时顺序无关(2.3.7 第 6 题) |
| 「设了尾指针就能 | ❌ | 删除尾结点要把前驱的 next 置空,仍需从头找, |
| 「在有序单链表中插入并保持有序是 | ❌ | 找插入位置 |
| 「给定 | ❌ | 最低 |
| 「长度为 | ❌ | 要遍历前面那个长度为 |
| 「头插法建立的链表与输入顺序相同」 | ❌ | 相反;尾插法才相同 |
| 「在线性表 | ⚠️ | 顺序存储移动 50 个,链式存储移动 0 个,答案是「0 或 50」 |
错题复盘:2016 用内存表表示单链表
表头元素为
的单链表存在内存中: (1008H)→ (1000H)→ (1010H)→ (1004H)→ (100CH)→NULL。 把存放于 1014H 的 插到 和 之间,问 、 、 的「链接地址」。 「链接地址」就是该结点 next所存的地址:改为指向 (1014H); 不变,仍指向 (1004H); 指向 (1010H)。答案是 1014H、1004H、1010H。 容易错把 也改掉。插入只改前驱和新结点两个 next,后继结点不动。
考点
- 头指针 vs 头结点,头结点的两个好处。
- 带头 / 不带头的判空条件。
- 插入两句的顺序;前插、删除给定结点的交换数据技巧。
- 头插法逆序、尾插法正序。
- 指针语句题逐句画图:2016、2024。
- 算法设计题:单链表是大题第二主力,模板见代码附录。
链接
- 🏠 返回总览:数据结构第 1~4 章总览
- ⬅️ 上一节:2.1~2.2 线性表与顺序表
- ➡️ 下一节:2.3.3~2.3.6 双链表、循环链表与静态链表
- 🔗 建表与插入删除的完整代码:板子:建表(头插法、尾插法)与插入删除
- 🔗 单链表的统考大题:2009 倒数第 k 个结点 · 2012 共同后缀 · 2015 按绝对值去重 · 2019 链表重排
- 🔗 单链表的基础题:就地逆置 · 删除值为 x 的结点 · 合并两个有序链表
- 📖 名词库:第 1~4 章名词库