链表的实现(单链表、双链表、环形表)【上】超详细!!
链表的相关概念链表在逻辑顺序上是连续的而在物理存储空间上不一定连续是一种线性的数据结构由一系列节点组成每一个节点包含两部分一个是数据域存储实际的数据另一个是指针域存储下一个节点的地址。常见类型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 阅读更多 →

最新新闻

页面能打开,不代表规则正确:单 HTML 游戏的纯规则核心与不变量测试

页面能打开,不代表规则正确:单 HTML 游戏的纯规则核心与不变量测试

我以前给浏览器小游戏做回归时,最容易得到一种虚假的安全感: 页面能打开;点击开始后没有报错;Canvas 不是空白;手机宽度没有横向滚动条。 这些都应该检查,但它们只能证明"界面大致活着"。 它们…

2026/7/23 2:51:21 阅读更多 →
SAGE框架:子目标条件化动作生成在强化学习规划中的应用

SAGE框架:子目标条件化动作生成在强化学习规划中的应用

在强化学习和机器人控制领域,如何让智能体在复杂环境中高效规划并执行动作一直是个核心挑战。传统的规划方法往往面临计算复杂度高或难以处理高维状态空间的困境。近期提出的 SAGE(Subgoal-Conditioned Action Generation)框架,通…

2026/7/23 2:51:21 阅读更多 →
Linear Loops自动化工作流:提升团队开发效率的完整指南

Linear Loops自动化工作流:提升团队开发效率的完整指南

在项目迭代和团队协作中,重复性的任务流转、状态同步和跨工具数据搬运往往消耗大量开发时间。Linear 最新推出的 Loops 功能,正是瞄准了这一痛点,旨在通过自动化工作流简化循环工程操作。本文将完整解析 Loops 的核心概念、适用场景&#xff…

2026/7/23 2:51:21 阅读更多 →
60%的知识库文档从未被检索过——你在用20%的文档回答100%的问题

60%的知识库文档从未被检索过——你在用20%的文档回答100%的问题

核心观点:知识库不是“越多越好”。我去跑了一个查询,盯了半天——六成的文档,过去30天一次都没被搜过。 我去做了件大多数人不会做的事:给知识库里每一条文档,查一下它过去30天被检索过多少次。 1200条FAQ。我按检索…

2026/7/23 2:51:21 阅读更多 →
开源软件商业化:从信任建立到价值变现的完整路径

开源软件商业化:从信任建立到价值变现的完整路径

开源软件到底能不能赚钱?这是很多开发者和创业公司都在思考的问题。最近看到一种观点:"开源首先解决的是信任问题,其次是流量。能不能赚钱取决于这东西有没有价值,是不是开源是其次的。"这句话看似简单,却道…

2026/7/23 2:51:21 阅读更多 →
NVIDIA Triton客户端安装与配置指南

NVIDIA Triton客户端安装与配置指南

1. NVIDIA Triton用户端软件安装指南作为AI推理服务的关键组件,NVIDIA Triton的用户端软件承担着与服务器通信、发送推理请求和接收结果的重要职责。不同于服务器端的复杂部署,用户端安装更注重开发环境的适配性。本文将详细介绍三种主流安装方式及其适用…

2026/7/23 2:50:21 阅读更多 →

日新闻

从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表)

从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表)

更多请点击: https://intelliparadigm.com 第一章:从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表) 当AI副业主理人不再仅满足于单次服务交付,而是主动构建可复用、可裂变、可…

2026/7/23 0:00:25 阅读更多 →
AI写作开头钩子设计:为什么你的AI文案完读率不足18%?——基于2,346篇A/B测试报告的归因分析

AI写作开头钩子设计:为什么你的AI文案完读率不足18%?——基于2,346篇A/B测试报告的归因分析

更多请点击: https://codechina.net 第一章:AI写作开头钩子设计:为什么你的AI文案完读率不足18%?——基于2,346篇A/B测试报告的归因分析 在对2,346篇跨行业AI生成文案的A/B测试数据进行聚类分析后,我们发现&#xff1…

2026/7/23 0:01:26 阅读更多 →
Chitchatter完整指南:免费开源的终极点对点安全聊天工具

Chitchatter完整指南:免费开源的终极点对点安全聊天工具

Chitchatter完整指南:免费开源的终极点对点安全聊天工具 【免费下载链接】chitchatter Secure peer-to-peer chat that is serverless, decentralized, and ephemeral 项目地址: https://gitcode.com/gh_mirrors/ch/chitchatter Chitchatter是一款革命性的安…

2026/7/23 0:01:26 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/7/22 12:54:44 阅读更多 →

月新闻