带头结点单链表头插法核心逻辑总结(基于代码实操)
#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(接入新结点),顺序不可反; - 动态内存:头结点仅初始化时
malloc1 次,每次插入新数据都需重新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。
评论区