带头结点单链表头插法核心逻辑总结(基于代码实操)
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// 数据元素类型(以int为例)
typedef int ElemType;
// 链表结点结构体定义
typedef struct LNode {
ElemType data;
struct LNode* next;
}LNode, * LinkList;
// -------------------------- 头插法相关 --------------------------
// 初始化带头结点的链表(供头插法使用)
bool InitList(LinkList& L) {
L = (LNode*)malloc(sizeof(LNode));
if (L == NULL) return false;
L->next = NULL;
return true;
}
// 头插法:插入元素e到头结点之后
bool HeadInsert(LinkList& L, ElemType e) {
LNode* s = (LNode*)malloc(sizeof(LNode));
if (s == NULL) return false;
s->data = e;
s->next = L->next; // s指向原首元结点
L->next = s; // 头结点指向s(新首元结点)
return true;
}
// -------------------------- 尾插法相关 --------------------------
// 初始化链表(含尾指针,供尾插法使用)
void InitListWithTail(LinkList& L, LNode*& r) {
L = (LNode*)malloc(sizeof(LNode));
if (L == NULL) exit(1);
L->next = NULL;
r = L; // 尾指针初始指向头结点
}
// 尾插法:插入元素e到链表末尾(需传入尾指针r)
bool TailInsert(LinkList& L, LNode*& r, ElemType e) {
LNode* s = (LNode*)malloc(sizeof(LNode));
if (s == NULL) return false;
s->data = e;
s->next = NULL; // 新尾结点next必为NULL
r->next = s; // 原尾结点连接新结点
r = s; // 尾指针更新为s
return true;
}
// -------------------------- 辅助函数 --------------------------
// 打印链表
void PrintList(LinkList L) {
LNode* p = L->next;
if (p == NULL) {
printf("链表为空\n");
return;
}
printf("链表元素:");
while (p != NULL) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
// 销毁链表(释放内存)
void DestroyList(LinkList& L) {
LNode* p = L;
while (p != NULL) {
LNode* q = p->next;
free(p);
p = q;
}
L = NULL;
}
// -------------------------- 测试主函数 --------------------------
int main() {
// 1. 测试头插法(插入1、2、3,预期结果:3 2 1)
LinkList L1;
InitList(L1);
HeadInsert(L1, 1);
HeadInsert(L1, 2);
HeadInsert(L1, 3);
printf("头插法结果(插入1、2、3):\n");
PrintList(L1); // 输出:3 2 1
DestroyList(L1);
// 2. 测试尾插法(插入1、2、3,预期结果:1 2 3)
LinkList L2;
LNode* r2; // 尾指针
InitListWithTail(L2, r2);
TailInsert(L2, r2, 1);
TailInsert(L2, r2, 2);
TailInsert(L2, r2, 3);
printf("\n尾插法结果(插入1、2、3):\n");
PrintList(L2); // 输出:1 2 3
DestroyList(L2);
return 0;
}
一、核心前提
头插法是将新结点插入到「头结点与原首元结点之间」,让新结点成为新首元结点的操作,核心是通过两步指针调整,确保新结点接入链表且原链表不中断,依赖 L->next
(头结点指针域)和 s->next
(新结点指针域)的协作。
二、关键概念定义(避免混淆)
- 头结点:
L
指向的结点,不存有效数据,仅用于统一链表操作(初始化时通过malloc
创建,整个链表仅 1 个); - 首元结点:头结点
L->next
指向的结点,是链表中第一个存有效数据的结点; - 原首元结点:插入新结点前,
L->next
原本指向的首元结点(记为p
); - 新结点
s
:每次插入时通过malloc
新分配的结点,用于存储待插入的有效数据e
。
三、头插法三步核心操作(对应代码)
假设插入前链表状态:头结点 L → 原首元结点 p → 后续结点 q → NULL
(若链表为空,则 L->next = NULL
,p
不存在)。
1. 第一步:给新结点存数据(s->data = e
)
- 作用:将待插入的有效数据
e
存入新结点s
的数据域data
; - 本质:为新结点 “填充内容”,否则新结点仅为空白内存,无法存储数据。
2. 第二步:新结点接住原首元结点(s->next = L->next
)
- 作用:将
L->next
中存储的原首元结点p
的地址,赋值给s->next
;- 若链表为空(
L->next = NULL
):则s->next = NULL
,新结点后续后续无结点; - 若链表非空:则
s->next
指向p
,确保原链表的p→q→...
部分不丢失(避免断链)。
- 若链表为空(
- 注意:此时
s
未接入链表,仅通过s->next
与原链表建立了关联(s
是 “游离结点”,链表入口L->next
仍指向p
)。
3. 第三步:头结点接入新结点(L->next = s
)
- 作用:将新结点
s
的地址赋值给L->next
,修改链表入口的指向; - 本质:这是新结点
s
正式 “加入链表” 的关键一步 —— 此时L->next
不再指向p
,而是指向s
,s
成为新的首元结点。
四、最终结果(链表结构变化)
操作阶段 | 链表结构(箭头表示指针指向) | 核心变化 |
---|---|---|
插入前 | L(头结点)→ p(原首元)→ q → NULL | L->next 指向 p |
执行 s->data = e |
L→p→q→NULL;s(存 e) | s 有数据,未关联链表 |
执行 s->next = L->next |
L→p→q→NULL;s→p | s 关联 p,仍游离 |
执行 L->next = s |
L→s(新首元)→ p→q→NULL | s 接入链表,成为新首元 |
五、核心原则
- 先接后断:必须先让
s->next
接住原首元结点(避免原链表丢失),再修改L->next
指向s
(接入新结点),顺序不可反; - 动态内存:头结点仅初始化时
malloc
1 次,每次插入新数据都需重新malloc
新结点s
(每个数据分配独立存储); - 指针本质:
L->next
和s->next
是 “存储地址的变量”,操作的是 “地址传递”,而非结点本身的移动。
尾插法核心逻辑
前提:主函数中的调用代码(先明确外部变量)
首先看主函数里怎么调用这两个函数的,后续流程都围绕这几个变量展开:
void InitListWithTail(LinkList& L, LNode*& r) {
L = (LNode*)malloc(sizeof(LNode));
if (L == NULL) exit(1);
L->next = NULL;
r = L; // 尾指针初始指向头结点
}
// 尾插法:插入元素e到链表末尾(需传入尾指针r)
bool TailInsert(LinkList& L, LNode*& r, ElemType e) {
LNode* s = (LNode*)malloc(sizeof(LNode));
if (s == NULL) return false;
s->data = e;
s->next = NULL; // 新尾结点next必为NULL
r->next = s; // 原尾结点连接新结点
r = s; // 尾指针更新为s
return true;
}
前提:主函数里先定义两个空指针
- 定义
LinkList L2
(等价于LNode* L2
):此时L2
没有指向任何内存(空指针); - 定义
LNode* r2
:此时r2
也没有指向任何内存(空指针)。
一、执行 InitListWithTail(L2, r2)
(初始化,实参为 L2
和 r2
)
L = (LNode*)malloc(sizeof(LNode));
- 作用:给形参
L
(L2
的引用,改L
即改L2
)分配一块内存,用来存 “头结点”; - 变量变化:
L2
从空指针,变为指向这块新内存(头结点); - 链表内容:
[头结点(刚分配,暂未设置 next)]
(仅存在头结点,未形成完整链表)。
- 作用:给形参
if (L == NULL) exit(1);
- 作用:检查内存是否分配成功,失败则退出程序,成功则继续;
- 变量变化:无(
L2
仍指头头结点); - 链表内容:不变(仍为
[头结点]
)。
L->next = NULL;
- 作用:操作
L2
指向的头结点,把它的next
设为NULL
(表示头结点后暂无数据节点); - 变量变化:头结点的
next
成员从随机值变为NULL
; - 链表内容:
[头结点] → NULL
(初始化完成,链表结构完整,无数据)。
- 作用:操作
r = L;
- 作用:给形参
r
(r2
的引用,改r
即改r2
)赋值,让r2
也指向头结点; - 变量变化:
r2
从空指针,变为指向头结点(与L2
指向同一位置); - 链表内容:不变(仍为
[头结点] → NULL
)。
- 作用:给形参
二、执行 TailInsert(L2, r2, 1)
(插入参数 1,实参为 L2
、r2
、1
)
LNode* s = (LNode*)malloc(sizeof(LNode));
- 作用:定义临时指针
s
,分配新内存存 “要插的节点”; - 变量变化:
s
指向这块新内存(暂未存数据); - 链表内容:不变(仍为
[头结点] → NULL
,新节点未接入)。
- 作用:定义临时指针
if (s == NULL) return false;
- 作用:检查新节点内存是否成功,失败返回
false
,成功继续; - 变量变化:无(
s
仍指向新内存); - 链表内容:不变(
[头结点] → NULL
)。
- 作用:检查新节点内存是否成功,失败返回
s->data = e;
- 作用:把参数
e
(即1
)存入s
指向的新节点; - 变量变化:新节点的
data
成员变为1
; - 链表内容:不变(新节点存了
1
,但未接入,仍为[头结点] → NULL
)。
- 作用:把参数
s->next = NULL;
- 作用:把新节点的
next
设为NULL
(它要当新末尾,末尾节点next
必为NULL
); - 变量变化:新节点的
next
成员变为NULL
; - 链表内容:不变(新节点结构完整,但未接入,仍为
[头结点] → NULL
)。
- 作用:把新节点的
r->next = s;
- 作用:
r
是r2
的引用,当前r2
指头结点,让头结点的next
指向s
(新节点),把1
接入链表; - 变量变化:头结点的
next
从NULL
变为指向新节点(存1
的节点); - 链表内容:变化为
[头结点] → [1] → NULL
(1
成功接在头结点后)。
- 作用:
r = s;
- 作用:更新
r2
,让r2
从 “头结点” 改指向s
(存1
的节点),使其成为新的末尾指针; - 变量变化:
r2
指向从 “头结点” 变为 “存1
的节点”; - 链表内容:不变(仍为
[头结点] → [1] → NULL
)。
- 作用:更新
return true;
- 作用:返回
true
表示插入成功; - 变量变化:无;
- 链表内容:最终为
[头结点] → [1] → NULL
。
- 作用:返回
三、执行 TailInsert(L2, r2, 2)
(插入参数 2,实参为 L2
、r2
、2
,此时 r2
指向存 1
的节点)
LNode* s = (LNode*)malloc(sizeof(LNode));
- 作用:分配新内存存 “要插的节点”,
s
指向它; - 变量变化:
s
指向新内存; - 链表内容:不变(
[头结点] → [1] → NULL
)。
- 作用:分配新内存存 “要插的节点”,
if (s == NULL) return false;
- 作用:检查内存,成功继续;
- 变量变化:无;
- 链表内容:不变。
s->data = e;
- 作用:把参数
2
存入s
指向的新节点; - 变量变化:新节点
data
为2
; - 链表内容:不变。
- 作用:把参数
s->next = NULL;
- 作用:新节点
next
设为NULL
; - 变量变化:新节点
next
为NULL
; - 链表内容:不变。
- 作用:新节点
r->next = s;
- 作用:
r
即r2
(指向存1
的节点),让存1
的节点next
指向新节点(存2
); - 变量变化:存
1
的节点next
从NULL
变为指向存2
的节点; - 链表内容:变化为
[头结点] → [1] → [2] → NULL
。
- 作用:
r = s;
- 作用:
r2
改指向存2
的节点(新末尾); - 变量变化:
r2
指向存2
的节点; - 链表内容:不变。
- 作用:
return true;
- 作用:插入成功;
- 链表内容:最终为
[头结点] → [1] → [2] → NULL
。
四、执行 TailInsert(L2, r2, 3)
(插入参数 3,实参为 L2
、r2
、3
,此时 r2
指向存 2
的节点)
LNode* s = (LNode*)malloc(sizeof(LNode));
- 作用:分配新内存存 “要插的节点”,
s
指向它; - 变量变化:
s
指向新内存; - 链表内容:不变(
[头结点] → [1] → [2] → NULL
)。
- 作用:分配新内存存 “要插的节点”,
if (s == NULL) return false;
- 作用:检查内存,成功继续;
- 变量变化:无;
- 链表内容:不变。
s->data = e;
- 作用:把参数
3
存入s
指向的新节点; - 变量变化:新节点
data
为3
; - 链表内容:不变。
- 作用:把参数
s->next = NULL;
- 作用:新节点
next
设为NULL
; - 变量变化:新节点
next
为NULL
; - 链表内容:不变。
- 作用:新节点
r->next = s;
- 作用:
r
即r2
(指向存2
的节点),让存2
的节点next
指向新节点(存3
); - 变量变化:存
2
的节点next
从NULL
变为指向存3
的节点; - 链表内容:变化为
[头结点] → [1] → [2] → [3] → NULL
。
- 作用:
r = s;
- 作用:
r2
改指向存3
的节点(新末尾); - 变量变化:
r2
指向存3
的节点; - 链表内容:不变。
- 作用:
return true;
- 作用:插入成功;
- 链表内容:最终为
[头结点] → [1] → [2] → [3] → NULL
。
评论区