【数据结构】万字深入浅出讲解单链表(附原码 | 超详解)

🚀write in front🚀
📝个人主页:认真写博客的夏目浅石.
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝
📣系列专栏:C语言实现数据结构
💬总结:希望你看完之后,能对你有所帮助,不足请指正!共同学习交流 🖊
✉️如果无聊的话,就来逛逛我的博客栈吧stack-frame.cn
文章目录
- 前言
- 一、链表概念
- 二、链表的分类
- 1. 单向或者双向
- 2. 带头或者不带头
- 3. 循环或者非循环
- 三、链表的实现
- 函数接口汇总SList.h
- 函数接口实现SList.c
- 代码实践Test.c
- 代码汇总
- 总结
前言
这几天看了数据结构的单链表这一节,真的收获很大,第一次看没有动手敲代码就是感觉学了和没学一样,今天也是从新又看了一遍,并且边学边敲代码,终于算是非常理解单链表这个东西了,今天就把我所学到的知识给大家分享一下。
一、链表概念
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链
接次序实现的 。

二、链表的分类
1. 单向或者双向

2. 带头或者不带头

3. 循环或者非循环

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
1.无头单向链表
2.双向循环链表

- 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
- 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
三、链表的实现
函数接口汇总SList.h
typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;//打印
void SLTPrint(SLTNode* phead);
SLTNode* BuySLTNode(SLTDataType x);
//增
void SLTPushBack(SLTNode** pphead,SLTDataType x);
void SLTPushFront(SLTNode** pphead,SLTDataType x);
//删
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);//查找
SLTNode* SListFind(SLTNode* phead,SLTDataType x);
//任意位置插入
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除某个节点
void SListErase(SLTNode** pphead, SLTNode* pos);
//在后面插入一个节点
void SListInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos后面这一个节点
void SListEraseAfter(SLTNode* pos);
函数接口实现SList.c
//--------------------------手动分割线---------------------------
//打印
void SLTPrint(SLTNode* phead)
{assert(phead);SLTNode* cur=phead;while(cur){printf("%d ",cur->data);cur=cur->next;}printf("NULL\n");
}
//--------------------------手动分割线-----------------------------
//创建新节点
SLTNode* BuySLTNode(SLTDataType x)
{SLTNode* newnode=(SLTNode*)malloc(sizeof(SLTNode));if(newnode==NULL) {printf("malloc fail\n");return NULL;}newnode->data=x;newnode->next=NULL;return newnode;
}
//--------------------------手动分割线-----------------------------
//尾插
void SLTPushBack(SLTNode** pphead,SLTDataType x)
{assert(pphead);SLTNode* newnode=BuySLTNode(x);if(*pphead==NULL){*pphead=newnode;}else{//找尾SLTNode* cur=*pphead;while(cur->next!=NULL){cur=cur->next;}cur->next=newnode;}
}
//--------------------------手动分割线-----------------------------
//前插
void SLTPushFront(SLTNode** pphead,SLTDataType x)
{assert(pphead);SLTNode* newnode=BuySLTNode(x);newnode->next=*pphead;*pphead=newnode;
}
//--------------------------手动分割线-----------------------------
//尾删
void SLTPopBack(SLTNode** pphead)
{// 暴力检查assert(pphead);assert(*pphead);SLTNode* cur=*pphead;if(*pphead->next==NULL){free(*pphead);*pphead=NULL;}else{SLTNode* cur=*pphead;while(cur->next->next!=NULL){cur=cur->next;}SLTNode* tmp=cur->next;free(tmp);cur->next=NULL;}
}
//--------------------------手动分割线-----------------------------
//前删
void SLTPopFront(SLTNode** pphead)
{assert(pphead);assert(*pphead);if(*pphead->next==NULL){free(*pphead);*pphead=NULL;}else{SLTNode* tmp=*pphead;free(tmp);tmp=NULL;*pphead=*pphead->next;}
}
//--------------------------手动分割线-----------------------------
//查找
SLTNode* SListFind(SLTNode* phead,SLTDataType x)
{SLTNode* cur=phead;while(cur){if(cur->data==x) return cur;cur=cur->next;}return NULL;
}
//--------------------------手动分割线-----------------------------
//任意位置插入
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pos);assert(pphead);SLTNode* newnode=BuySLTNode(x);if(*pphead==pos){SLTPushFront(pphead, x);}else{SLTNode* prev=*pphead;while(prev->next!=pos){prev=prev->next;}prev->next=newnode;newnode->next=pos;}
}
//--------------------------手动分割线-----------------------------
//删除某个节点
void SListErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(pos);SLTNode* cur=*pphead;if(*pphead==pos){SLTPopFront(pphead);}else{while(cur->next!=pos){cur=cur->next;}cur->next=pos->next;free(pos);pos=NULL;}
}
//--------------------------手动分割线-----------------------------
//在后面插入一个节点
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = BuySLTNode(x);newnode->next = pos->next;pos->next = newnode;
}
//--------------------------手动分割线-----------------------------
//删除pos后面这一个节点
void SListEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}
//--------------------------手动分割线-----------------------------
代码实践Test.c
#include"SList.h"void TestSList1()
{SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);
}void TestSList2()
{SLTNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);
}void TestSList3()
{SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);
}int main()
{TestSList3();return 0;
}
代码汇总
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;//--------------------------手动分割线---------------------------
//打印
void SLTPrint(SLTNode* phead)
{assert(phead);SLTNode* cur=phead;while(cur){printf("%d ",cur->data);cur=cur->next;}printf("NULL\n");
}
//--------------------------手动分割线-----------------------------
//创建新节点
SLTNode* BuySLTNode(SLTDataType x)
{SLTNode* newnode=(SLTNode*)malloc(sizeof(SLTNode));if(newnode==NULL) {printf("malloc fail\n");return NULL;}newnode->data=x;newnode->next=NULL;return newnode;
}
//--------------------------手动分割线-----------------------------
//尾插
void SLTPushBack(SLTNode** pphead,SLTDataType x)
{SLTNode* newnode=BuySLTNode(x);if(*pphead==NULL){*pphead=newnode;}else{//找尾SLTNode* cur=*pphead;while(cur->next!=NULL){cur=cur->next;}cur->next=newnode;}
}
//--------------------------手动分割线-----------------------------
//前插
void SLTPushFront(SLTNode** pphead,SLTDataType x)
{SLTNode* newnode=BuySLTNode(x);newnode->next=*pphead;*pphead=newnode;
}
//--------------------------手动分割线-----------------------------
//尾删
void SLTPopBack(SLTNode** pphead)
{//暴力检查assert(*pphead);SLTNode* cur=*pphead;if(*pphead->next==NULL){free(*pphead);*pphead=NULL;}else{SLTNode* cur=*pphead;while(cur->next->next!=NULL){cur=cur->next;}SLTNode* tmp=cur->next;free(tmp);cur->next=NULL;}
}
//--------------------------手动分割线-----------------------------
//前删
void SLTPopFront(SLTNode** pphead)
{assert(*pphead);if(*pphead->next==NULL){free(*pphead);*pphead=NULL;}else{SLTNode* tmp=*pphead;free(tmp);tmp=NULL;*pphead=*pphead->next;}
}
//--------------------------手动分割线-----------------------------
//查找
SLTNode* SListFind(SLTNode* phead,SLTDataType x)
{SLTNode* cur=phead;while(cur){if(cur->data==x) return cur;cur=cur->next;}return NULL;
}
//--------------------------手动分割线-----------------------------
//任意位置插入
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{SLTNode* newnode=BuySLTNode(x);if(*pphead==pos){SLTPushFront(pphead, x);}else{SLTNode* prev=*pphead;while(prev->next!=pos){prev=prev->next;}prev->next=newnode;newnode->next=pos;}
}
//--------------------------手动分割线-----------------------------
//删除某个节点
void SListErase(SLTNode** pphead, SLTNode* pos)
{SLTNode* cur=*pphead;if(*pphead==pos){SLTPopFront(pphead);}else{while(cur->next!=pos){cur=cur->next;}cur->next=pos->next;free(pos);pos=NULL;}
}
//--------------------------手动分割线-----------------------------
//在后面插入一个节点
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = BuySLTNode(x);newnode->next = pos->next;pos->next = newnode;
}
//--------------------------手动分割线-----------------------------
//删除pos后面这一个节点
void SListEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}
//--------------------------手动分割线-----------------------------void TestSList1()
{SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);
}void TestSList2()
{SLTNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);
}void TestSList3()
{SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);
}int main()
{TestSList3();return 0;
}
总结
今天学习了单链表的知识,初次写数据结构的知识,给我的感觉就是,学三遍不如手敲代码一遍来的实在,所以数据结构的学习我将多画图,多敲代码来学习,希望大家吸取经验和我一起学习数据结构,为后面打比赛刷题打下坚实基础。
我是夏目浅石,希望和你一起学习进步,刷题无数!!!希望各位大佬能一键三连支持一下博主,hhhh~我们下期见喽

如果无聊的话,就来逛逛我的博客栈吧stack-frame.cn
✨原创不易,还希望各位大佬支持一下\textcolor{blue}{原创不易,还希望各位大佬支持一下}原创不易,还希望各位大佬支持一下
👍 点赞,你的认可是我创作的动力!\textcolor{9c81c1}{点赞,你的认可是我创作的动力!}点赞,你的认可是我创作的动力!
⭐️ 收藏,你的青睐是我努力的方向!\textcolor{ed7976}{收藏,你的青睐是我努力的方向!}收藏,你的青睐是我努力的方向!
✏️ 评论,你的意见是我进步的财富!\textcolor{98c091}{评论,你的意见是我进步的财富!}评论,你的意见是我进步的财富!
相关文章:
【数据结构】万字深入浅出讲解单链表(附原码 | 超详解)
🚀write in front🚀 📝个人主页:认真写博客的夏目浅石. 🎁欢迎各位→点赞👍 收藏⭐️ 留言📝 📣系列专栏:C语言实现数据结构 💬总结:希望你看完…...
无线WiFi安全渗透与攻防(五)之aircrack-ng破解WEP加密
系列文章 无线WiFi安全渗透与攻防(一)之无线安全环境搭建 无线WiFi安全渗透与攻防(二)之打造专属字典 无线WiFi安全渗透与攻防(三)之Windows扫描wifi和破解WiFi密码 无线WiFi安全渗透与攻防(四)之kismet的使用 aircrack-ng破解WEP加密 1.WEP介绍 其实我们平常在使用wifi的时…...
MySQL中事务的相关问题
事务 一、事务的概述: 1、事务处理(事务操作):保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit…...
推荐算法再次踩坑记录
去年搞通了EasyRec这个玩意,没想到今年还要用推荐方面的东西,行吧,再来一次,再次踩坑试试。1、EasyRec训练测试数据下载:git clone后,进入EasyRec,然后执行:bash scripts/init.sh 将…...
STM32 (十五)MPU6050
简介前言一、MPU6050简介MPU6050是一款性价比很高的陀螺仪,可以读取X Y Z 三轴角度,X Y Z 三轴加速度,还有内置的温度传感器,在姿态解析方面应用非常广泛。下面是它在淘宝上的参数图产品尺寸产品参数产品原理图:二、硬…...
使用yarn,依赖报各种错误怎么办
使用 yarn^3.x 版本时,默认并不会安装包到 node_modules,因为 yarn3.x 是即插即用的,也就是说如果你下载过这个包,yarn只会生成一个 Png文件,然后将包的路径 link 到下载过的地方,这样可以省去很多时间。而…...
面试官:rem和vw有什么区别
"rem" 和 "vw"的区别 "rem" 和 "vw" 都是用于网页设计的CSS单位。 "rem" 是相对于根元素的字体大小来计算的单位,即相对于 "html" 标签的字体大小。例如,如果 "html" 标签的字…...
【GPT-4】GPT-4 相关内容总结
目录 编辑 官网介绍 GPT-4 内容提升总结 GPT-4 简短版总结 GPT-4 基础能力 GPT-4 图像处理 GPT-4 技术报告 训练过程 局限性 GPT-4 风险和应对措施 开源项目:OpenAI Evals 申请 GPT-4 API API的介绍以及获取 官网介绍 官网:GPT-4 API候…...
5.springcloud微服务架构搭建 之 《springboot集成Hystrix》
1.springcloud微服务架构搭建 之 《springboot自动装配Redis》 2.springcloud微服务架构搭建 之 《springboot集成nacos注册中心》 3.springcloud微服务架构搭建 之 《springboot自动装配ribbon》 4.springcloud微服务架构搭建 之 《springboot集成openFeign》 目录 1.项目…...
【工作中问题解决实践 七】SpringBoot集成Jackson进行对象序列化和反序列化
去年10月份以来由于公司和家里的事情太多,所以一直没有学习,最近缓过来了,学习的脚步不能停滞啊。回归正题,其实前年在学习springMvc的时候也学习过Jackson【Spring MVC学习笔记 五】SpringMVC框架整合Jackson工具,但是…...
香港服务器遭受DDoS攻击后如何恢复运行?
您是否发现流量异常上升?您的网站突然崩溃了吗?当您注意到这些迹象时,可能是在陷入了DDoS攻击的困境,因而,当开始考虑使用香港服务器时,也应该考虑香港服务器设备受DDoS攻击时,如何从中恢复。 在 DDoS 攻击香港…...
【Hive】配置
目录 Hive参数配置方式 参数的配置方式 1. 文件配置 2. 命令行参数配置 3. 参数声明配置 配置源数据库 配置元数据到MySQL 查看MySQL中的元数据 Hive服务部署 hiveserver2服务 介绍 部署 启动 远程连接 1. 使用命令行客户端beeline进行远程访问 metastore服务 …...
IP-GUARD如何强制管控电脑设置开机密码要符合密码复杂度?
如何强制管控电脑设置开机密码要符合密码复杂度? 7 可以在控制台-【策略】-【定制配置】,添加一条配置,开启系统密码复杂度检测。 类别:自定义 关键字:bp_password_complexity 内容:1 效果图:...
剑指 Offer II 031. 最近最少使用缓存
题目链接 剑指 Offer II 031. 最近最少使用缓存 mid 题目描述 运用所掌握的数据结构,设计和实现一个 LRU(Least Recently Used,最近最少使用) 缓存机制 。 实现 LRUCache类: LRUCache(int capacity)以正整数作为容量 capacity初始化 LRU缓…...
44岁了,我从没想过在CSDN创作2年,会有这么大收获
1998年上的大学,02年毕业,就算从工作算起,我也有20余年的码龄生涯了。 但正式开启博文的写作,却是2021年开始的,差不多也就写了2年的博客,今天我来说说我在CSDN的感受和收获。 我是真的没想到,…...
相位相参信号源的设计--示波器上的信号不稳定,来回跑?
目录乱跑的波形边沿触发触发方式外部触发相参与非相参相位相参的射频信号源样机外观与内部设计软件设计上位机软件信号源使用方法PWM触发信号射频信号的时域波形射频信号的频谱输出功率在示波器的实际使用当中波形在示波器的时域上乱跑,左右移动,定不下来…...
Spring Boot 整合 RabbitMQ 多种消息模式
Spring Boot 整合 RabbitMQ 多种消息模式 准备工作集成 RabbitMQ发布/订阅模式点对点模式主题模式总结Spring Boot 是一个流行的 Java 应用程序开发框架,而 RabbitMQ 是一款可靠的消息队列软件。将 Spring Boot 和 RabbitMQ 结合起来可以帮助我们轻松地实现异步消息传递。Rabb…...
node多版本控制
前言 最近在折腾Python,并将node升级至v18.14.2。突然发现一个旧项目无法运行,也无法打包,里面的node-sass报错,显然这是因为node版本过高导致的。 将node版本降低至以前的v14.16.0,果然立马就能正常运行。 存在不同…...
Redis set集合
Redis set (集合)遵循无序排列的规则,集合中的每一个成员(也就是元素,叫法不同而已)都是字符串类型,并且不可重复。Redis set 是通过哈希映射表实现的,所以它的添加、删除、查找操作…...
漫画:什么是希尔排序算法?
希尔排序(ShellSort)是以它的发明者Donald Shell名字命名的,希尔排序是插入排序的改进版,实现简单,对于中等规模数据的性能表现还不错 一、排序思想 前情回顾:漫画:什么是插入排序算法…...
相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...
全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...
浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...
三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...
多模态图像修复系统:基于深度学习的图片修复实现
多模态图像修复系统:基于深度学习的图片修复实现 1. 系统概述 本系统使用多模态大模型(Stable Diffusion Inpainting)实现图像修复功能,结合文本描述和图片输入,对指定区域进行内容修复。系统包含完整的数据处理、模型训练、推理部署流程。 import torch import numpy …...
jmeter聚合报告中参数详解
sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample(样本数) 表示测试中发送的请求数量,即测试执行了多少次请求。 单位,以个或者次数表示。 示例:…...
Linux中《基础IO》详细介绍
目录 理解"文件"狭义理解广义理解文件操作的归类认知系统角度文件类别 回顾C文件接口打开文件写文件读文件稍作修改,实现简单cat命令 输出信息到显示器,你有哪些方法stdin & stdout & stderr打开文件的方式 系统⽂件I/O⼀种传递标志位…...
