怎样免费做网站,走出趣网站怎么做,湛江北京网站建设,企业注册平台别再被数组虐哭#xff01;C语言链表双雄#xff1a;单链表循环链表通俗到爆#xff0c;小白也能秒懂上手#xff01;
你是不是也被数组逼到过崩溃边缘#xff1f;想往中间插个数据#xff0c;得把后面所有元素挨个挪位置#xff0c;累得像搬砖#xff1b;想删个数据C语言链表双雄单链表循环链表通俗到爆小白也能秒懂上手你是不是也被数组逼到过崩溃边缘想往中间插个数据得把后面所有元素挨个挪位置累得像搬砖想删个数据又要费劲补窟窿生怕留下空位更气人的是数组长度固定死多存一个数据都不行少存了又浪费空间别急C语言里藏着个“动态排队大师”——链表早就把这些破事解决得明明白白而链表家族里单链表和循环链表就像一对性格迥异的兄弟一个耿直boy只认往前冲到尾就停一个恋家小弟绕圈跑不停永远能回到起点。今天咱们就用唠嗑的方式把这俩兄弟扒得底朝天代码大白话双管齐下没有复杂术语没有晦涩逻辑小白也能看懂看完直接上手写一、先搞懂核心链表的“基本操作单元”——节点不管是单链表还是循环链表都是由“节点”串起来的。节点这玩意儿其实就是个“多功能快递盒”里面整整齐齐分两格一格装正经东西数据域比如1、2、3这些数字也能换成字符、结构体啥的看你需要另一格装下一个快递盒的地址指针域相当于快递盒上贴了张纸条写着“下一个快递盒在这儿”不然链表怎么知道该往哪走用C语言写出来就是这样单链表和循环链表通用直接抄就行// 通用节点结构体单链表/循环链表都能用typedefstructNode{intdata;// 数据域装整型数据想换类型直接改这儿structNode*next;// 指针域存下一个节点的地址}Node,*LinkedList;// Node节点类型LinkedList节点指针类型简化写法简单说链表就是把一个个这样的“快递盒”通过指针域的“地址纸条”串起来形成的动态队伍——想加人就加个快递盒想删人就把纸条改了不用挪动其他快递盒主打一个灵活二、单链表 vs 循环链表核心区别大揭秘这俩兄弟看着像但核心差异就两点尾节点的“归宿”和遍历的“终止规则”用大白话对比一下一看就懂特性单链表耿直boy循环链表恋家小弟尾节点指针指向NULL相当于排队排到最后后面没人了直接站定不动主打一个“到点下班”指向头节点排到最后发现前面是队首直接绕回去形成一个圈主打一个“永不散场”遍历终止条件走到NULL就停到终点了打道回府不往前走了走回头节点就停绕圈跑回到起点就收工绝不多跑一步空表判断头指针是NULL队伍里一个人都没有空荡荡的头节点的next指向自己哨兵大哥自己跟自己玩没人来排队尾节点访问得从头走到尾跑遍全场才能找到最后一个人费点劲头节点的前面就是尾节点哨兵大哥后面是队首前面就是队尾逻辑清晰不用瞎跑适用场景单向走、频繁在开头或中间插人删人比如食堂打饭有人插队到前面或者中间有人吃完走了绕圈走、频繁找队首队尾比如操场跑步、进程调度一直循环转离不开首尾其实说白了循环链表就是单链表的“升级版”——把单链表的尾节点从“指向NULL”改成“指向头节点”直接变身“闭环队伍”其他核心逻辑都差不多三、单链表耿直boy的“单向排队指南”单链表是链表家族的“基础款”节点一个个单向串联尾节点指向NULL只能从头部往尾部走不能回头就像耿直boy一样一条路走到黑。1. 单链表的6个核心操作代码大白话1创建节点造一个新的“快递盒”// 创建一个新节点返回节点指针失败返回NULL没地方放快递盒了Node*create_node(intdata){Node*new_node(Node*)malloc(sizeof(Node));// 申请内存找地方放快递盒if(new_nodeNULL){// 没找到地方申请失败perror(malloc failed);returnNULL;}new_node-datadata;// 把数据装进快递盒new_node-nextNULL;// 暂时没下一个快递盒先留空returnnew_node;}大白话创建节点就是“造快递盒”先找块内存放盒子再把数据装进去最后告诉它“暂时没下一个盒子”造不出来就报错简单粗暴2头插法插队到最前面高效逆序插入头插法就是新节点直接插到队伍最前面相当于食堂打饭有人插队不用管后面的人效率超高但插入顺序和最终顺序是反的。// 头插法将数据插入链表头部返回新的头指针插队成功新人为队首LinkedListinsert_head(LinkedList head,intdata){Node*new_nodecreate_node(data);// 先造个新快递盒if(new_nodeNULL)returnhead;// 造失败了队伍不变new_node-nexthead;// 新盒子的下一个地址指向原来的队首returnnew_node;// 新盒子成为新队首}例子先头插1再头插2最后头插3结果是3-2-1-NULL相当于插队的人越来越多后来的反而在前面3尾插法乖乖排到队尾顺序插入尾插法就是新节点排到队伍最后得先找到队尾nextNULL的节点再把新节点接上插入顺序和最终顺序一致就像排队乖乖排到最后。// 尾插法将数据插入链表尾部返回头指针队首不变LinkedListinsert_tail(LinkedList head,intdata){Node*new_nodecreate_node(data);// 造新快递盒if(new_nodeNULL)returnhead;// 造失败队伍不变if(headNULL){// 队伍是空的新盒子直接当队首returnnew_node;}Node*phead;// 从队首开始找while(p-next!NULL){// 一直找到队尾后面没人的那个pp-next;}p-nextnew_node;// 队尾指向新盒子新盒子排到最后returnhead;}例子尾插1再尾插2再尾插3结果是1-2-3-NULL规规矩矩插多少就按顺序排多少4遍历单链表从头走到尾“打卡”遍历就是把链表的所有数据都看一遍从队首开始一个个往后走直到走到NULL队尾。// 遍历并打印单链表所有节点打卡报到voidtraverse_singly(LinkedList head){if(headNULL){// 队伍是空的printf(链表为空\n);return;}Node*phead;// 从队首开始while(p!NULL){// 没走到队尾就一直走printf(%d - ,p-data);// 打印当前数据相当于打卡pp-next;// 往下一个节点走}printf(NULL\n);// 打印队尾标志告诉大家“到这儿就结束了”}5删除节点把不想见的人“请出队伍”按值删除找到第一个符合条件的节点把它从队伍里去掉后面的人接上避免队伍断开。// 删除第一个值为data的节点返回新头指针如果删的是队首队首会变LinkedListdelete_node(LinkedList head,intdata){if(headNULL)returnNULL;// 队伍是空的没的删Node*tempNULL;if(head-datadata){// 要删的是队首temphead;// 记下队首的位置headhead-next;// 队首往后移一位free(temp);// 把原来的队首“请走”释放内存returnhead;}Node*phead;// 从队首开始找// 找要删节点的前一个人且没走到队尾while(p-next!NULLp-next-data!data){pp-next;}if(p-next!NULL){// 找到了要删的节点tempp-next;// 记下要删的节点p-nexttemp-next;// 前一个人直接指向后一个人跳过要删的节点free(temp);// 把要删的节点“请走”}returnhead;}大白话删除节点就像队伍里有人要走要么是队首走了后面的人顶上要么是中间的人走了前面的人直接拉着后面的人不让队伍断开6释放单链表内存把“快递盒”都回收链表用完了得把申请的内存都释放掉不然就像借了别人的东西不还内存会被占满内存泄漏程序会崩掉// 释放整个单链表内存回收所有快递盒voidfree_singly(LinkedList head){Node*phead;while(p!NULL){// 一个个回收直到队尾Node*tempp;// 记下当前快递盒pp-next;// 往下一个走free(temp);// 回收当前快递盒}}2. 单链表示例完整使用演示直接抄来跑intmain(){LinkedList headNULL;// 初始化队伍空的// 尾插法构建链表1-2-3-NULL乖乖排队headinsert_tail(head,1);headinsert_tail(head,2);headinsert_tail(head,3);traverse_singly(head);// 输出1 - 2 - 3 - NULL// 头插法插入44-1-2-3-NULL4插队到最前面headinsert_head(head,4);traverse_singly(head);// 输出4 - 1 - 2 - 3 - NULL// 删除值为2的节点4-1-3-NULL把2从队伍里请出去headdelete_node(head,2);traverse_singly(head);// 输出4 - 1 - 3 - NULLfree_singly(head);// 回收所有快递盒不浪费内存return0;}运行结果和预期一致一步步看着链表从无到有再到插入、删除最后回收整个过程明明白白四、循环链表恋家小弟的“闭环排队指南”循环链表是单链表的“变种”核心改动就一个尾节点的next不指向NULL而是指向头节点形成一个闭环就像恋家的小弟绕圈跑再远最后都会回到起点。推荐用“带头节点”的循环链表头节点是“哨兵大哥”不存数据只负责管理队伍这样能简化空表和非空表的操作减少出错概率1. 循环链表的5个核心操作代码大白话1初始化循环链表创建“哨兵大哥”// 初始化带头节点的循环链表返回头节点指针创建哨兵大哥LinkedListinit_circular(){Node*head(Node*)malloc(sizeof(Node));// 给哨兵大哥找个位置if(headNULL){// 没找到位置创建失败perror(malloc failed);returnNULL;}head-nexthead;// 空表时哨兵大哥自己跟自己玩next指向自己returnhead;}大白话初始化循环链表就是先找个“哨兵大哥”空表的时候他自己站着有人来排队了就把人插在他后面最后一个人再指向他形成闭环2尾插法插入节点排到队尾绕回哨兵循环链表尾插不用找NULL找“哨兵大哥的前一个节点”就是队尾插入后新节点再指向哨兵保持闭环。// 尾插法向循环链表插入数据带头节点voidinsert_circular_tail(LinkedList head,intdata){if(headNULL)return;// 没有哨兵大哥插不了Node*new_nodecreate_node(data);// 造新快递盒if(new_nodeNULL)return;// 造失败插不了Node*phead;while(p-next!head){// 找队尾哨兵大哥的前一个就是队尾pp-next;}p-nextnew_node;// 队尾指向新盒子new_node-nexthead;// 新盒子指向哨兵保持闭环}大白话循环链表尾插就像排队绕圈排到最后一个人后面就是哨兵大哥新来人就站在最后一个人后面再回头指向哨兵圈子不变3遍历循环链表绕圈打卡回到哨兵就停// 遍历并打印循环链表带头节点voidtraverse_circular(LinkedList head){if(headNULL||head-nexthead){// 没哨兵或哨兵自己玩空表printf(循环链表为空\n);return;}Node*phead-next;// 从队首开始跳过哨兵大哥while(p!head){// 没回到哨兵就一直绕圈printf(%d - ,p-data);// 打卡报到pp-next;}printf((回到头节点)\n);// 提示绕圈结束回到哨兵了}4删除节点从闭环中“请人走”和单链表删除逻辑类似但终止条件是“没回到哨兵大哥”删除后还要保持闭环。// 删除循环链表中第一个值为data的节点带头节点voiddelete_circular(LinkedList head,intdata){if(headNULL||head-nexthead)return;// 空表没的删Node*phead;// 找要删节点的前一个人且没回到哨兵while(p-next!headp-next-data!data){pp-next;}if(p-next!head){// 找到了要删的节点Node*tempp-next;// 记下要删的节点p-nexttemp-next;// 前一个人指向后一个人跳过要删的节点free(temp);// 把要删的节点“请走”}}5释放循环链表内存先删小弟再删哨兵循环链表要先释放所有数据节点小弟们最后再释放头节点哨兵大哥不然会漏释放内存// 释放带头节点的循环链表先删小弟再删哨兵voidfree_circular(LinkedList head){if(headNULL)return;// 没有哨兵不用删Node*phead-next;while(p!head){// 先删所有数据节点Node*tempp;pp-next;free(temp);}free(head);// 最后删哨兵大哥}2. 循环链表示例完整使用演示直接抄来跑intmain(){LinkedList clistinit_circular();// 初始化创建哨兵大哥// 插入节点1-2-3-(回到哨兵)绕圈排队insert_circular_tail(clist,1);insert_circular_tail(clist,2);insert_circular_tail(clist,3);traverse_circular(clist);// 输出1 - 2 - 3 - (回到头节点)// 删除值为2的节点1-3-(回到哨兵)把2请出圈子delete_circular(clist,2);traverse_circular(clist);// 输出1 - 3 - (回到头节点)free_circular(clist);// 先删小弟再删哨兵回收所有内存return0;}运行后能看到循环链表一直绕圈删除节点后还是闭环完美保持“永不散场”的特性五、单链表 vs 循环链表深度对比一目了然除了前面的核心区别再把操作细节对比一下选的时候不纠结操作单链表循环链表带头节点初始化头指针初始为NULL队伍空着头节点next指向自身哨兵自己玩尾插法遍历到p-nextNULL找队尾遍历到p-next头节点找哨兵的前一个遍历终止pNULL到队尾p头节点回到哨兵空表判断headNULL没人排队head-nexthead哨兵自己玩尾节点访问O(n)跑遍全场O(n)同样跑全场但逻辑清晰环形遍历不支持走到NULL就停绕不了圈天然支持绕圈跑回到哨兵就停六、踩坑预警这4个坑千万别踩血的教训循环链表的死循环遍历的时候别把终止条件写成p!NULL不然就像绕着操场跑步不知道停程序直接卡死正确的是p头节点回到哨兵插入/删除后一定要让尾节点指向头节点不然闭环断了就不是循环链表了内存泄漏用malloc申请的节点用完必须free单链表要一个个回收循环链表要先回收数据节点再回收头节点漏一个都不行不然内存越用越少最后程序崩掉空表处理单链表空表是headNULL循环链表空表是head-nexthead操作前一定要先判断空表不然会访问NULL指针程序直接报错头节点的价值循环链表一定要用头节点哨兵大哥能统一空表和非空表的操作逻辑不用单独处理“插入到队首”的情况减少边界错误新手必用七、总结该选单链表还是循环链表选单链表如果只是单向遍历、频繁在头部或中间插人删人比如实现栈、简单任务队列、临时存数据选单链表就够了结构简单实现起来不费劲选循环链表如果需要环形遍历、频繁访问队首队尾比如约瑟夫环问题、进程调度、循环缓冲区选循环链表闭环特性直接适配场景不用额外处理其实这俩兄弟核心操作都差不多就差在“终止条件”和“尾节点处理”学会单链表后把尾节点从指向NULL改成指向头节点再调整一下终止条件分分钟搞定循环链表现在再回头看链表是不是没那么难代码大白话一步步拆解小白也能看懂上手。下次再被数组虐直接用链表解决灵活又高效赶紧动手试试吧