链表的实现(单链表、双链表、环形表)【上】超详细!!
链表的相关概念链表在逻辑顺序上是连续的而在物理存储空间上不一定连续是一种线性的数据结构由一系列节点组成每一个节点包含两部分一个是数据域存储实际的数据另一个是指针域存储下一个节点的地址。常见类型1.单链表每个节点指向下一个节点。2.双链表每个节点同时指向前驱与后继。3.循环链表尾节点指回头节点形成环。适用场景一般为1.频繁插入/删除数据2.不需要随机访问元素在内存空间不连续查找元素需要从头开始逐个遍历运行效率低实现栈、队列、图等更复杂的数据结构。其与顺序表的区别在于1.存储结构上顺序表为连续内存链表分散内存2.空间分配上顺序表预分配可能会有空间的浪费链表内存按需动态申请3.查找上顺序表内存连续按值查找支持随机访问链表内存不连续只能通过指针接力挨个查找效率较低。4.在插入删除当中顺序表需要整体移动多个元素造成程序性能的消耗而链表效率高只需要修改指针5.在缓存当中顺序表连续内存命中率高缓存友好性号链表内存分散缓存不友好。单链表的实现1.定义单链表结构typedef int SLDataType; typedef struct SListNode { SLDataType data; struct SListNode*next; }SListNode;在定义完单链表结构后我们创建一个函数CreteNode用来创建链表节点以便我们在vs及时观察调试void CreateNode() { SListNode* node1 (SListNode*)malloc(sizeof(SListNode)); node1-data 1; SListNode* node2 (SListNode*)malloc(sizeof(SListNode)); node2-data 2; SListNode* node3 (SListNode*)malloc(sizeof(SListNode)); node3-data 3; SListNode* node4 (SListNode*)malloc(sizeof(SListNode)); node4-data 4; node1-next node2; node2-next node3; node3-next node4; node4-next NULL; }在链表中没有增容的概念需要插入数据就直接申请一块新的空间动态申请的空间指针类型为void*所以需要强制类型转换成相应的指针类型接着调试监视node1,观察单链表是否创建成功由图可知链表创建成功。创建成功后试着用一个函数将其打印出来void SLprint(phead) { SListNode* pcur phead; while (pcur) { printf(%d-, pcur-data); pcur pcur-next; } printf(NULL\n); }刚刚做的测试只是为了验证定义链表结构是否正确因此创建链表调试观察其是否符合预期一般来说创建链表并不像CreatNode函数这样创建而是插入到空链表当中。2.链表的头插以及尾插在进行插入操作增加新的数据都需要开辟新的空间将这一步单独抽离开来重新定义一个函数单独来实现SListNode*SLBuyNode(SLDataType x);SListNode*SLBuyNode(SLDataType x) { SListNode* newnode (SListNode*)malloc(sizeof(SListNode)); newnode-data x; newnode-next NULL; return newnode; }在写完SLBuyNode函数后进行尾插操作void SLPushBack(SListNode** pphead,SLDataType x) { assert(pphead); SListNode* newnode SLBuyNode(x); SListNode* pcur *pphead; while (pcur-next) { pcur pcur-next; } pcur-next newnode; }之后在test函数里面进行测试函数放回值为0说明程序正常运行打印出插入后的链表。但这里有个问题我们是在已知链表的基础上进行操作那假如链表为NULL呢这种情况就应该进行特殊处理void SLPushBack(SListNode** pphead,SLDataType x) { assert(pphead); SListNode* newnode SLBuyNode(x); if (*pphead NULL) { *pphead newnode; } else { SListNode* pcur *pphead; while (pcur-next) { pcur pcur-next; } pcur-next newnode; } }用一个函数调试测试运行void test01() { SListNode* node NULL; SLPushBack(node,1); SLPushBack(node,2); SLPushBack(node,3); SLPushBack(node,4); SLPushBack(node,5); SLprint(node); } int main() { //SListNode* phead CreateNode(); test01(); return 0; }函数返回值为0程序正常运行。接下来为头插对于头插操作我们依旧需要调用SLBuyNode函数申请一块新的空间将新申请节点的next指针指向我原来的节点*pphead将新申请的空间地址作为我单链表的新节点即*pphead newnodevoid SLPushFront(SListNode** pphead, SLDataType x) { assert(pphead); SListNode* newnode SLBuyNode(x); if (*pphead NULL) { *pphead newnode; } else { newnode-next *pphead; *pphead newnode; } }这里需要注意的是1.newnode-next *pphead 2.*pphead newnode这里的顺序是不能进行颠倒的因为一旦先*pphead newnode此时在newnode-next *pphead*pphead指向的就不是原来的头节点了而是申请新节点地址。3.单链表的头删和尾删对于尾删SLPopBack我们需要注意的是保存最后一个节点的上一个节点位置free释放掉最后一个节点以及不能对空链表进行尾删操作//尾删 void SLPopBack(SListNode** pphead) { assert(pphead *pphead); SListNode* pcur *pphead; SListNode* ptail NULL; while (pcur-next-next) { pcur pcur-next; ptail pcur-next; } free(ptail); ptail NULL; pcur-next NULL; }pcur-next-next是指pcur下一个节点的下一个节点当pcur-next-next指针为NULL时也就意味这pcur走到了最后一个节点的上一个位置除此之外我们不能对空链表执行删除操作所以代码如下//尾删 void SLPopBack(SListNode** pphead) { assert(pphead *pphead); if ((*pphead)-nextNULL) { free(*pphead); *pphead NULL; } else { SListNode* pcur *pphead; SListNode* prev NULL; while (pcur-next) { prev pcur; pcur pcur-next; } prev-next NULL; free(pcur); pcur NULL; } }测试、运行程序运行成功尾删执行完成。在尾删操作当中如果删到最后一个元素时此时没有前一个节点prev了如果我们对prev解引用属于非法访问了所以我们需要对只有一个节点的情况另行判断只剩一个节点相当于头删操作直接释放这个空间但我们需要用*pphead因为这是通过内存地址直接进行操作会对原链表造成影响如果是直接freepcur在打印最后一个NULL时会出现随机的垃圾值这是因为pcur只是一个临时变量出了函数周期不会对链表造成影响那为什么else分支里面的prev也是临时变量会对链表造成影响呢因为prev-next NULL;操作是通过地址去操作的并且将节点置为NULL后逻辑上切断了该节点的连续性所以else分支里面的操作是可以影响链表。如果我们尾删完了所有数据此时链表为空依旧执行删除操作呢代码会因为assert断言终止程序。对于头删而言逻辑代码相对简洁主要是需提前保存第一个节点的下一个节点然后再去释放第一个节点空间void SLPopFront(SListNode** pphead) { assert(pphead *pphead); SListNode* next (*pphead)-next; free(*pphead); *pphead next; }4.查找SListNode* SLFind(SListNode*phead, SLDataType x) { SListNode* pcur phead; while (pcur) { if (pcur-data x) { printf(找到了\n); return pcur; } pcur pcur-next; } printf(NULL\n); }5.在指定位置之前插入数据在指定位置之前插入数据需要找到该节点的前一个节点然后改变节点指向另外一个需要注意的情况可能链表只有一个数据此时需要找的pos节点恰好为该节点即头插此时调用头插函数即可void SLInsert(SListNode** pphead,SListNode* pos,SLDataType x) { assert(pphead*pphead); //SListNode* pcur *pphead; assert(pos); if (*pphead pos) { SLPushFront(pphead, x); } else { SListNode* prev *pphead; SListNode* newnode SLBuyNode(x); while (prev-next ! pos) { prev prev-next; } newnode-next pos; prev-next newnode; } }对于在test.c测试文件中我们需要调用查找函数利用函数的返回值如果查找的数不存在返回NULL此时pos为NULL程序会终止运行6.在指定位置之后插入数据在指定位置之后插入数据传参不需要头节点因为有pos就可以找得到下一个节点不过再写代码的时候需要特别注意1.newnode-next pos-next;2.pos-next newnode;顺序不能动因为一旦代码先运行2那么pos-next指针就变了不是原来的节点了。//在指定位置之后插入数据 void SLInsertAfter(SListNode* pos, SLDataType x) { assert(pos); SListNode* newnode SLBuyNode(x); newnode-next pos-next; pos-next newnode; }调试、运行:7.删除指定位置节点在这一步当中对于非头尾节点的节点来说受到影响的为前一个节点以及后一个节点所以我们需要遍历找到这个要删除的节点然后让上一个节点prev的下一个节点指向newnode的下一个节点然后free掉我们要删除的节点newnode但我们放到test测试文件里面进行测试时发现尾节点也能正常删除但头节点却不适用这是因为头节点没有前置节点prev了这时候我们需要另外判断这种情况当需要删除的节点恰好为头节点时此时为头删直接调用头删函数即可。//删除指定位置的节点 void SLErase(SListNode** pphead,SLDataType x) { SListNode* newnode SLFind(*pphead,x); assert(pphead newnode); SListNode* prev *pphead; if (prev newnode) { SLPopFront(pphead); } else { while (prev-next ! newnode) { prev prev-next; } prev-next newnode-next; free(newnode); newnode NULL; } }测试、运行8.删除指定位置之后的节点在这里的逻辑实现相对简单不过需要注意的是删除指定位置的下一个节点不能为NULL//删除指定位置之后的节点 void SLEraseAfter(SListNode** pos) { assert(pos *pos); assert((*pos)-next); SListNode* del (*pos)-next; (*pos)-next (*pos)-next-next; free(del); del NULL; }

相关新闻

C++测试框架实战指南:Google Test与Catch2核心对比与应用

C++测试框架实战指南:Google Test与Catch2核心对比与应用

1. 项目概述:为什么C开发者需要一个好用的测试框架? 如果你写过C,尤其是写过稍微有点规模的C项目,大概率经历过这种场景:改了一个看似无关紧要的Bug,结果引发了另一个模块的雪崩式崩溃;或者信心…

2026/7/21 23:57:24 阅读更多 →
粉笔行测“模块化提分法“:先保底再拔高的科学路径

粉笔行测“模块化提分法“:先保底再拔高的科学路径

行测提分的核心在于按模块推进、分阶段突破,而非对所有题型均匀用力。粉笔公考提出的"模块化提分法"正是基于这一认知,通过"先保底、再拔高"的科学路径,帮助考生在有限备考时间内实现分数最大化。这一方法经过粉笔多年教…

2026/7/21 23:56:23 阅读更多 →
Spring Boot3整合MyBatis-Plus实战避坑指南

Spring Boot3整合MyBatis-Plus实战避坑指南

1. Spring Boot3与MyBatis-Plus整合概述在Java企业级开发领域,Spring Boot3作为最新一代的微服务框架,与MyBatis-Plus这一强大的ORM工具的结合,已经成为现代Java后端开发的黄金组合。这套技术栈能够显著提升开发效率,但在实际整合…

2026/7/21 23:56:23 阅读更多 →

最新新闻

Unity 2D游戏场景氛围营造:雨夜效果实现与优化

Unity 2D游戏场景氛围营造:雨夜效果实现与优化

最近在整理项目时,发现很多开发者对2D游戏开发中的场景氛围营造感到头疼——特别是如何用简单的技术手段实现复杂的视听体验。今天通过一个具体的2D游戏场景《听夜雨》的开发案例,分享如何用基础技术打造沉浸式环境氛围。这个案例的核心价值在于&#xf…

2026/7/22 1:42:28 阅读更多 →
多Agent协作架构:原理、实践与2026趋势

多Agent协作架构:原理、实践与2026趋势

1. 多Agent协作架构的核心价值2026年的技术生态正在经历一场从单体智能到群体协作的范式转移。单Agent系统在处理复杂任务时面临三大瓶颈:上下文窗口限制、专业领域知识单一、任务分解能力不足。多Agent协作架构通过角色分工和协同机制,实现了11>2的智…

2026/7/22 1:42:28 阅读更多 →
DyberPet:基于PySide6的模块化桌面宠物框架设计与实现

DyberPet:基于PySide6的模块化桌面宠物框架设计与实现

DyberPet:基于PySide6的模块化桌面宠物框架设计与实现 【免费下载链接】DyberPet Desktop Cyber Pet Framework based on PySide6 项目地址: https://gitcode.com/GitHub_Trending/dy/DyberPet DyberPet是一个基于PySide6构建的开源桌面宠物框架,…

2026/7/22 1:42:28 阅读更多 →
英语(一)心理描写-固定搭配—东方仙盟

英语(一)心理描写-固定搭配—东方仙盟

一、情绪心理类短语(15 句)be ashamed ofYou have nothing to be ashamed of. 你没有什么值得羞愧的。He is ashamed of his careless mistakes in the exam. 他为考试里粗心犯下的错误感到惭愧。Don’t be ashamed of asking questions when studying E…

2026/7/22 1:42:28 阅读更多 →
2026最新5款企业AI编程工具选型深度对比实测

2026最新5款企业AI编程工具选型深度对比实测

作为一名在企业做后端开发已经五年的工程师,我最近半年一直在帮团队评估适合企业场景的AI编程工具。毕竟现在大模型时代,AI辅助编码已经不是要不要用的问题,而是选哪款工具既能提升效率又能满足企业的安全合规要求。TRAE是字节跳动出品的国内…

2026/7/22 1:42:28 阅读更多 →
如何利用EPANET开源工具包进行供水管网水力与水质分析

如何利用EPANET开源工具包进行供水管网水力与水质分析

如何利用EPANET开源工具包进行供水管网水力与水质分析 【免费下载链接】EPANET The Water Distribution System Hydraulic and Water Quality Analysis Toolkit 项目地址: https://gitcode.com/gh_mirrors/ep/EPANET 在当今城市水资源管理和管网系统优化中,E…

2026/7/22 1:41:27 阅读更多 →

日新闻

TI DSP系统配置模块SYSCFG详解:中断机制与主设备优先级配置实战

TI DSP系统配置模块SYSCFG详解:中断机制与主设备优先级配置实战

1. 项目概述与SYSCFG模块的核心价值在嵌入式系统,尤其是像TI C6000系列这样的高性能DSP开发中,我们常常会与芯片手册里那些密密麻麻的寄存器打交道。很多开发者可能更关注算法实现、内存优化或者外设驱动,但对于一个稳定、高效的系统而言&…

2026/7/22 0:00:26 阅读更多 →
微信Server酱:高到达率的应急通知方案实践

微信Server酱:高到达率的应急通知方案实践

1. 为什么我们需要"最次"的通知方案? 在数字化协作环境中,消息通知系统的重要性不言而喻明。但现实情况是,企业级通知方案往往需要复杂的API对接(如企业微信、钉钉、飞书),个人开发者的小项目又经…

2026/7/22 0:00:26 阅读更多 →
甲方要的“简洁“PPT,到底是简洁还是省事?

甲方要的“简洁“PPT,到底是简洁还是省事?

甲方说"简洁一点",乙方听到的是"少做几页"。甲方说"不要太复杂",乙方理解成"别放图表了"。结果交过去,甲方说"我说的简洁不是这个意思"。"简洁"这个词在PPT语境里,是…

2026/7/22 0:00:26 阅读更多 →

周新闻

Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/21 8:48:31 阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/21 5:34:47 阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/21 8:25:39 阅读更多 →

月新闻