当前位置: 首页 > news >正文

单链表超详细精讲|带头节点带头指针双实现 + 核心备份思想 + 完整可运行c语言源码 - Fa-Mian

单链表

1.带头节点的单链表

前置知识

​ 先展示下链表的样子(这里我加了了个count 给写成了结构体) (count用来统计链表中的节点数量)

image-20260529220023643

1.插入相关

插入新节点:1.引入辅助指针p,p找到待插入位置的前一个位置
2.创建新节点
3.更新新节点
4.最后更新老节点任意位置的插入,找到这个位置,效率低
头结点上插入(非常快直接插效率高),尾插入 先找到第n个元素然后再插(效率低)
  1. 引入辅助指针p,p找到待插入位置的前一个位置 ------> node_t *p=前驱节点
  2. 创建新节点 ------> node_t *new_node = malloc(sizeof(node_t));

image-20260528204321619

3.更新新节点 ------> new_node->next = p->next;

image-20260528204722576

  1. 最后更新老节点 p->next = new_node

image-20260528205148867

1.引入辅助指针p,p找到待插入位置的前一个位置
2.创建新节点
3.更新新节点
4.最后更新老节点
node_t *p=前驱节点                                
node_t *new_node = malloc(sizeof(node_t))
new_node->next = p->next; 
p->next = new_node

2.删除相关

删除某个节点:1.引入辅助指针p,p找到待删除位置的前一个节点
2.引入辅助指针备份待删除位置 tmp
3.修改前驱节点的  next  指针,跳过待删除节点,重新衔接链表
4.删除tmp
  1. 引入辅助指针p,p找到待删除位置的前一个节点 ------> node_t *p = 前驱节点

  2. 引入辅助指针备份待删除位置 tmp ------> node_t *tmp = p->next;

image-20260528211057014

  1. 修改前驱节点的 next 指针,跳过待删除节点,重新衔接链表 ------> p->next = tmp->next;

image-20260528211215358

  1. 删除tmp ------> free(tmp);

image-20260528211803537

1.引入辅助指针p,p找到待删除位置的前一个节点
2.引入辅助指针备份待删除位置 tmp
3.修改前驱节点的 next 指针,跳过待删除节点,重新连接链表
4.删除tmp
node_t* p = 前驱节点
node_t* tmp = p->next;
p->next = tmp->next;
free(tmp);

3.额外补充一点(备份思想)

image-20260529213506169

//如果我们把插入的代码反过来写呢
p->next = new_node
new_node->next = p->next;     
//可以看到完全错误 //p->next在第一步就被更新了 即c的位置找不到了
换句话说就是c的位置特别的重要 即p->next的位置 一旦更改了就找不到后面节点的位置了 所以我们要先备份c的位置
new_node->next = p->next;   //这一步相当于在备份p->next (等号右边)
p->next = new_node          //备份了就可以用了
删除相关的代码也也用到了备份思想 这里就不过多赘述了

有些人一旦错过就不在

头文件部分

#include <stdio.h>
#include <stdlib.h>typedef int Element_t;   
//1 定义链表的节点结构
typedef struct _node {Element_t val;        //该节点的数据域struct _node* next;   //指向下一个节点的指针
} node_t;//2 定义带头节点的链表头结构
typedef struct {node_t head;int count;            //存储链表中的节点的数量
}LinkList_t;//1 创建头节点
LinkList_t *createLinkList();			
//2 销毁单链表
void releaseLinkList(LinkList_t *table);	//3 头插
int insertLinkListHeader(LinkList_t *table, Element_t val);
//4 在指定的pos位置插
int insertLinkListPos(LinkList_t *table, int pos, Element_t val);
//5 删除链表中的一个指定的值
int deleteLinkListElement(LinkList_t *table, Element_t val);
//6 显示单链表中的所有的内容
void showLinkList(const LinkList_t *table); 

实现

1.创建头节点

LinkList_t* createLinkList() {//申请头节点LinkList_t* table = NULL;                    table = malloc(sizeof(LinkList_t));if (table == NULL) {return NULL;}//头节点初始化table->head.val = 0;table->head.next = NULL;table->count = 0;//返回该头节点return table;
}

2.销毁单链表

void releaseLinkList(LinkList_t *table) {if (table) {//从头节点后的第一个节点开始删除node_t* p = &table->head;     //定义辅助指针node_t* temp; while (p->next) {//删除相关代码temp = p->next;p->next = temp->next;free(temp);//删除后减少链表中的节点数量--table->count;}//节点删除完后 看下链表中的节点数量 正确应该为0printf("LinkList have %d node! \n", table->count);//最后释放头节点free(table);}
}

3.头插

int insertLinkListHeader(LinkList_t* table, Element_t val) {//引入辅助指针p 指向头节点     可以把头节点当成要插入节点的前一个位置 node_t* p = &table->head;             //申请新节点的空间node_t* new_node = malloc(sizeof(node_t));                 if (new_node == NULL) {return -1;}//插入代码 new_node->val = val;new_node->next = p->next;p->next = new_node;//头插后 增加链表中节点的数量++table->count;return 0;
}

4.在指定的pos位置插

int insertLinkListPos(LinkList_t* table, int pos, Element_t val) {// 1. 判断边界值if (pos < 0 || pos > table->count) {printf("insert pos invalid!\n");return -1;}// 2. 找到pos - 1索引的节点首地址node_t *p = &table->head;    int index = -1;           //头节点的位置认为为-1//开始循环while (index < pos - 1) {// 遍历链表,找到 pos-1 位置的节点p = p->next;++index;}if (p == NULL) {return -1;}//循环结束index的位置在待插入位置的请一个位置 即此时index=pos-1//p在插入待位置的前一个位置//申请新节点的空间node_t *new_node = malloc(sizeof(node_t));//插入代码new_node->val = val;new_node->next = p->next;p->next = new_node;//插入后 增加链表中节点的数量++table->count;return 0;
}

5. 删除链表中的一个指定的值

int deleteLinkListElement(LinkList_t* table, Element_t val) {//引入辅助指针指向头节点node_t* p = &table->head;                       //向后遍历单链表中的节点while (p->next) {      // 判断下一节点是否为待删除目标值if (p->next->val == val) {//找到了指定的要删除的节点(值) 退出循环break;}p = p->next;}if (p->next == NULL) {printf("Not Find!\n");return -1;}//此时就找到了待删除的节点 即p->next指的位置 p是待删除节点的前驱节点//删除代码 node_t* temp = p->next;   p->next = temp->next;     free(temp);     //删除节点后 减少链表中节点的数量--table->count;return 0;
}

6.显示表中的所有的内容

void showLinkList(const LinkList_t* table)        //这里加const是因为 该链表是只读的不可修改
{//引入辅助指针p (这里指向的是要展示的第一个节点 )  node_t* p = table->head.next;           printf("link List: %d\n", table->count);//向后遍历单链表中的节点 并 打印while (p) {printf("%d ", p->val);  p = p->next;}printf("\n");
}

测试案例

main函数

void test01() {printf("link Table!\n");//创建带头结点的空链表LinkList_t* table = createLinkList();if (table == NULL) {return ;}//循环头插10个节点for (int i = 0;i < 10;i++) {insertLinkListHeader(table, i + 100);}//插入指定位置节点insertLinkListpos(table, 1, 520);showLinkList(table);printf("=============================\n");//根据值删除指定的节点deleteLinkListElement(table, 520);//展示整个表中节点的值showLinkList(table);//释放整个表releaseLinkList(table);
}int main()
{test01();return 0;
}

输出结果

link Table!
link List: 11
109 520 108 107 106 105 104 103 102 101 100
=============================
link List: 10
109 108 107 106 105 104 103 102 101 100
LinkList have 0 node!

2.带头指针的单链表

前置知识

​ 处理带头指针的单链表 最万金油的思路 :引入辅助节点,充当头节点 用完就扔掉它
​ (所谓带头指针的单链表就是 头指针指向第一个节点)

​ 先展示下链表的样子(这里我加了了个count 给写成了结构体) (count用来统计链表中的节点数量)

image-20260529220952038

引入辅助节点,充当头节点

image-20260529222415546

1.插入相关

插入新节点:
1.引入辅助节点,充当头节点
2.引入辅助指针p,p找到待插入位置的前一个位置
3.创建新节点
4.更新新节点
5.最后更新老节点
node_t dummy;       //辅助头节点 放到栈区用完自动回收
dummy.next = header;  //此时dunmmy.next 就指向链表中的第一个元素//这一部分就和带头节点的插入代码一样了
node_t *p = &dummy;   
node_t* new_node = malloc(sizeof(node_t));
new_node->next = p->next;
p->next = new_node;// 辅助头节点(dummy)是栈局部变量,函数结束会自动销毁,需把新链表表头同步给原链表
table->header = dummy.next;   

2.删除相关

删除某个节点:
1.引入辅助节点,充当头节点
2.引入辅助指针p,p找到待删除位置的前一个节点
3.引入辅助指针备份待删除位置 tmp
4.修改前驱节点的  next  指针,跳过待删除节点,重新衔接链表
5.删除tmp
node_t dummy;               //辅助头节点 放到栈区用完自动回收
dummy.next = table->header; //此时dunmmy.next 就指向链表中的第一个元素//这一部分就和带头节点的删除代码一样了
node_t* p = &dummy;
while(p){... p = p->next ...};        //找到待删除节点的前一个节点
node_t* tmp = p->next;
p->next = tmp->next;
free(tmp);// 辅助头节点(dummy)是栈局部变量,函数结束会自动销毁,需把新链表表头同步给原链表
table->header = dummy.next;   

头文件部分

#include <stdio.h>
#include <stdlib.h>typedef int Element_t;   
//1 定义链表的节点结构
typedef struct _node {Element_t val;        //该节点的数据域struct _node* next;   //指向下一个节点的指针
} node_t;//2 定义带头指针的链表头结构  
typedef struct {node_t* header;int count;
}ChainList_t;//1 表头放到数据区 放到全局变量 (换一个思想)
void initChainList(ChainList_t* table);
//2 销毁该链表的元素区域,头置空
void destoryChainList(ChainList_t* table);      //3 头插
int insertChainListHeader(ChainList_t* table, Element_t val);
//4 在指定的pos位置插
int insertChainListpos(ChainList_t* table, int pos, Element_t val);
//5 删除链表中的一个指定的值
int deleteChainListElement(ChainList_t* table, Element_t val);
//6 显示链表中的所有内容
void showChainList(const ChainList_t* table);

实现

带头指针的代码由于和带头节点的代码高度相似 注释尽量简略了 只写些主要的理清逻辑

1.初始化头节点

void initChainList(ChainList_t* table) 
{table->count = 0;table->header = NULL;               //初始表中没节点 置空  
}

2.销毁单链表

void destoryChainList(ChainList_t* table) {node_t dummy;dummy.next = table->header;  //删除代码node_t* p = &dummy;node_t* temp;while (p->next) {temp = p->next;p->next = temp->next;free(temp);--table->count;}printf("LinkList have %d node! \n", table->count);table->header = NULL;            //头置空
}

3.头插

int insertChainListHeader(ChainList_t* table, Element_t val) {node_t dummy;dummy.next = table->header;                 //插入代码node_t* p = &dummy;                      node_t* new_node = malloc(sizeof(node_t));                 if (new_node == NULL) {return -1;}new_node->val = val;new_node->next = p->next;p->next = new_node;++table->count;//更新table->header = dummy.next;   return 0;
}

4.在指定的pos位置插

int insertChainListpos(ChainList_t* table, int pos, Element_t val) {node_t dummy;dummy.next = table->header;//1 判断边界值if (pos<0 || pos>table->count) {printf("insert pos invalid");return -1;}//2 找到pos-1索引的节点地址node_t* p = &dummy;   int index = -1;while (index < pos - 1) {p = p->next;++index;}if (p == NULL) {return -1;}//插入代码node_t* new_node = malloc(sizeof(node_t));new_node->val = val;new_node->next = p->next;p->next = new_node;++table->count;//更新table->header = dummy.next;return 0;
}

5. 删除链表中的一个指定的值

int deleteChainListElement(ChainList_t* table, Element_t val) {node_t dummy;dummy.next = table->header;node_t* p = &dummy;while (p->next) {                                 if (p->next->val == val) {break;}p = p->next;}/* while (p->next && p->next->val != val) {p = p->next;} */if (p->next == NULL) {printf("Not Find!\n");}//删除代码node_t* temp = p->next;p->next = temp->next;free(temp);--table->count;//更新table->header = dummy.next;return 0;
}

6.显示表中的所有的内容

void showChainList(const ChainList_t* table)      //这里加const是因为 该链表是只读的不可修改
{node_t* p = table->header;    printf("link List: %d\n", table->count);while (p) {printf("%d ", p->val);p = p->next;}printf("\n");
}

测试案例

main函数

//带头指针的单向链表测试//ChainList_t stu1;
void testc02() {ChainList_t stu1;//初始化带头指针的单链表initChainList(&stu1);     //循环头插10个节点for (int i = 0;i < 10;i++) {insertChainListHeader(&stu1, i + 100);}//展示整个表中的节点showChainList(&stu1);//指定位置插入节点 并展示表中的节点insertChainListpos(&stu1, 1, 520);showChainList(&stu1);printf("=============================\n");//根据值删除指定的节点deleteChainListElement(&stu1, 520);//展示整个表中的节点showChainList(&stu1);//销毁该表的元素区域 头置空destoryChainList(&stu1);
}int main()
{test02();return 0;
}

输出结果

link List: 10
109 108 107 106 105 104 103 102 101 100
link List: 11
109 520 108 107 106 105 104 103 102 101 100
=============================
link List: 10
109 108 107 106 105 104 103 102 101 100
LinkList have 0 node!

​ 嘻嘻嘻嘻 单链表部分到此结束😆😆

​ (有错误欢迎指出) (疑问也是)❤️❤️😍😍💖💖

http://www.gsyq.cn/news/1436783.html

相关文章:

  • 2026 西安高端酒水上门回收无套路正规实体门店口碑榜单 - 速递信息
  • 抖音下载器终极指南:从零开始掌握批量下载的完整方案
  • 产业转移新版图:中西部10座城市正在接走哪类东部工厂
  • 2026 西安高端酒水回收哪家靠谱 同城高价无套路门店人气榜单 - 速递信息
  • 2026年泉州装修/旧房翻新领域优选服务商深度分析报告 - 速递信息
  • 南京黄金回收商家实力榜 5.31大盘价 + 11 区上门实测,靠谱首选 - 速递信息
  • 为什么你的Gemini微调总失败?92%工程师踩中的4个训练数据陷阱(附可复用清洗脚本)
  • 自动驾驶毫米波雷达中的CFAR:如何用MATLAB/Simulink搭建目标检测模型?
  • 2人新疆旅游旅行社排行 纯玩定制服务实测对比 - 互联网科技品牌测评
  • Gemini股东大会材料终极对照表:对比GPT-5闭门会议纪要、Claude 4路线图,锁定2024唯一可落地的AI集成窗口期
  • 【独家首发】Google内部泄露的Gemini 2.0能力边界白皮书(含未公开基准测试数据)
  • 2026 西安高端老酒高价回收 陈年茅台名酒正规机构排名 - 速递信息
  • RAG 与知识图谱在根因分析中的协同
  • Go语言测试与质量保障
  • 【Gemini应用更新日志深度解码】:20年AI平台运维专家亲授5大被忽略的兼容性雷区及迁移避坑清单
  • 基于Arduino与PID控制的智能平衡系统设计与实现
  • Go语言构建与部署最佳实践
  • Gemini会员活动效果归因困局:用因果森林模型替代UTM,精准定位高价值动作链(附Python可执行代码包)
  • 国内头部猎头公司实测排行:中高端服务能力深度对比 - 得赢
  • D2DX:为经典《暗黑破坏神2》注入现代生命力的魔法桥梁
  • 终极塞尔达传说存档管理器:简单快速实现Switch与WiiU存档互转
  • Roto一周年:新特性、新机制、新应用,编译型脚本语言发展正当时!
  • VinXiangQi:智能象棋AI连线工具的终极创新方案
  • 服务稳定性达99.995%,成本降低32%——Gemini升级实测报告,仅限首批认证开发者获取
  • 运维测试人员转网安必看:转行方向 + 方法 + 避坑指南
  • Gemini账号彻底删除操作手册:从界面点击到服务器级数据擦除的12个关键节点验证
  • Claude Code效率翻倍的秘密:老程序员压箱底的快捷键圣经
  • 2026 电动快枪盘 vs 气动快换盘 vs 气动换枪盘|焊接与通用快换全场景对比推荐(源头厂家实测) - GrowthUME
  • 实时风控延迟突破800ms?Gemini模型轻量化改造实录:FP16+结构剪枝+ONNX Runtime加速,端到端压降至42ms
  • 戴森球计划工厂蓝图库:5000+模块化工业设计解决方案深度解析