首页/新闻资讯/正文详情

链表核心操作实战:单链表与双向链表的插入删除及逆序详解

发布时间:2026/9/25 3:04:47 来源:云帆数科 栏目:资讯中心
链表核心操作实战:单链表与双向链表的插入删除及逆序详解
数组和链表哪个更好这是我在带学生做数据结构实验时被问得最多的问题。单链表和双向链表作为数据结构课程里的绝对主角不仅考研408年年考期末试卷次次出更是后续栈、队列、图论的基础。我见过太多同学拿着严蔚敏的C语言版教材盯着指针的箭头符号发呆或者写完插入函数一运行就崩溃排查半天发现是顺序写反了。这篇文章我打算换个讲法直接把我自己写链表总结出来的那套“肌肉记忆”分享出来单链表怎么建、怎么插、怎么删双向链表那根多出来的指针到底图什么以及实验报告和考试代码题里的高频考点怎么准备。别担心我是从哪里抄来的抽象理论这里所有内容都是我实际调试过、跑通过、也在考场上验证过的东西。不管你是正在准备408考研还是赶期末实验报告又或者是想彻底搞懂链表这层窗户纸的自学者读完应该都能按照我给的思路从零到一写出能跑、能交、能拿分的代码。1. 为什么链表始终是数据结构绕不开的门槛1.1 数组与链表两种存储哲学的第一次正面PK很多人初学链表时的第一个困惑是数组用得好好的下标访问O(1)写起来又简单为什么非要引入链表这个麻烦东西要回答这个问题得先弄清楚数组的核心矛盾连续存储。连续存储意味着访问任何元素都只需要“基地址 下标 × 元素大小”一次计算但这同样意味着插入和删除要移动大量元素。你在数组中间插一个数后面的所有元素都要往后挪移动复杂度是O(n)。数组还需要一段连续的空闲内存申请一个100MB的数组如果内存碎片把空间切成了一段段30MB申请就直接失败。链表靠的是“不连续存储也能存取数据”的思路。每个节点既存数据也存一个指向下一个节点的指针数据分散在内存各处靠指针串成一条链。插入和删除只需要修改指针指向不需要搬动元素时间复杂度降到O(1)——前提是你已经站在了目标节点旁边。这种用指针换连续性的思想是数据结构里“逻辑结构和物理结构分离”的第一次真正实践。我当时学到这里才意识到编程不是只知道怎么用现成工具而是要理解数据在内存里的组织方式如何决定了算法的性能天花板。1.2 单链表、双向链表、循环链表三个兄弟的定位与分工链表面向不同需求演化出了三个常用形态。单链表是最朴素的形态每个节点有一个next指针只能从头往后走适合“只往后追加、顺序遍历”的场景。双向链表在节点里多了一个prior或prev指针既能往后走也能往前走代价是每个节点多占一个指针的内存但换来的是“找到某个节点后前插和前删都变成O(1)”这个特性在频繁做中间插入删除的场合非常实用。循环链表则把尾节点的next指向头节点让整条链首尾相接适合表示轮询、约瑟夫环这类循环数据。我给学生打过一个比方单链表像单向车道只能往前开双向链表像双向车道可以随时掉头循环链表像环形跑道跑完一圈还能继续跑。考试里最常出的组合其实是“双向循环链表”也就是既带prev指针又首尾相连比如操作系统的进程管理、内存块管理里都是这种结构。我建议学链表时把这三个形态一起学因为它们之间只差一两个指针的指向问题一次搞明白后面就不用反复回头补课。1.3 从考研408到期末实验链表考点的真实分布从近几年的考研408考情看链表几乎是每年必考。客观题喜欢考带头节点和不带头节点对插入删除的影响、链表和数组的复杂度对比代码大题则常出单链表的逆序、合并两个有序链表、查找倒数第k个节点这类题目。期末实验课里“在指定位置插入建立单链表”“单链表的清空”“循环单链表的遍历”则是老熟人老师基本都会给你一个框架让你补齐核心函数。实际上这些题背后都是同一套指针操作基本功我后面会把这些操作的细节都拆开讲。想拿高分光会背代码不够得真明白每一个指针赋值语句在干什么画图推演到肌肉记忆。我在教学生时总强调一句话链表的代码题画图是解法的第一生产力。你只要能画出指针变化图代码就是照着图翻译。2. 单链表核心实现从建表到逆序的逐步拆解2.1 结构体定义与头节点为什么头节点这么重要单链表的节点定义我建议统一用这种写法typedef struct LNode { int data; // 数据域 struct LNode *next; // 指针域 } LNode, *LinkList;这里有两个容易卡壳的点。一是为什么用typedef不写typedef你在创建节点时每次都要写struct LNode非常啰嗦写成LinkList后LNode是节点类型LinkList是节点指针类型读代码时也更直观。二是什么叫头节点通常我们定义一个LinkList L然后让L指向一个不存数据的头节点L-next才指向第一个真正存数据的节点。这个头节点的作用是统一插入和删除的代码逻辑在头部插入节点时普通节点的插入代码也能直接复用不需要单独写一个“插到最前面”的特殊分支删除第一个数据节点时也不必额外判断“删的是不是第一个”。不带头节点的链表当然也能写但边界处理更繁琐考试和实验里绝大多数都要求带头节点。我的建议简单直接凡是自己能决定就一律带头节点。它让代码量和出错概率都下降一半。如果题目明确说不带头节点你再考虑去掉这个头节点后的边界变化。2.2 头插法与尾插法两种建表方式的优劣对比创建单链表有两条经典路线。头插法是在头节点后面不断插入新节点新节点永远成为第一个数据节点。核心代码是这样的void createListHead(LinkList L, int a[], int n) { L-next NULL; // 初始为空表 for (int i 0; i n; i) { LNode *s (LNode *)malloc(sizeof(LNode)); s-data a[i]; s-next L-next; // 新节点指向原来的第一个节点 L-next s; // 头节点指向新节点 } }仔细看这个过程如果用数组a[1,2,3]去创建最终链表里存的数据顺序是3,2,1。为什么因为每次新节点都插在头部后来的数据反而在前面。所以头插法常被用来实现“逆序建表”比如要把一个序列倒序存储直接头插一遍就能完成时间复杂度O(n)。尾插法则是维持一个尾指针r每次都把新节点挂在链尾数据顺序和输入顺序完全一致。实现时注意每插入一个节点就要更新r最后还要把r-next置为NULL否则尾部会悬空。void createListTail(LinkList L, int a[], int n) { LNode *r L; // r指向尾节点初始是头节点 for (int i 0; i n; i) { LNode *s (LNode *)malloc(sizeof(LNode)); s-data a[i]; r-next s; // 挂到链尾 r s; // 更新尾指针 } r-next NULL; }如果你发现创建完链表后遍历打印结果总是倒序的那基本就是错用了头插想得到正序结果。每次调用malloc后记得检查返回值是否为NULL这在嵌入式或者内存紧张的OJ环境里是常见失分点。2.3 指定位置插入与删除边界条件的魔鬼细节这是单链表操作里最核心、也最容易出bug的部分。在指定位置i插入节点先要把指针移动到i-1的位置然后执行“先连后断”新节点先指向后继前驱再指向新节点。bool insertList(LinkList L, int i, int data) { LNode *p L; int j 0; while (p ! NULL j i - 1) { // 找到第i-1个节点 p p-next; j; } if (p NULL) return false; // i不合法 LNode *s (LNode *)malloc(sizeof(LNode)); s-data data; s-next p-next; p-next s; return true; }请记住一个铁律插入节点时永远先改新节点的next再改前驱的next。你要是反过来先把p-next指给s那原来的后继节点就丢了链就断了。删除操作要简单些找到第i-1个节点p用q记下p-next然后让p-next指向q-next最后free(q)。边界条件主要防两件事一是i太大或太小导致p提前走到NULL二是删除时p-next本身就是NULL——表示没有可删的节点。这两种情况都必须返回false不能继续往下执行。我在改学生实验代码时看到最多的错误就是循环条件里的j和i谁先谁后搞混。我自己习惯这么记插入到第i个位置要找第i-1个节点找到后循环停下的标志是j i - 1所以循环的条件是j i - 1。你可以画一条小链子把指针移动的过程标出来做一次就会刻进脑子里。2.4 单链表逆序迭代法的三步走逻辑逆序是链表代码题里出场率最高的题目。面试考、考研考、笔试考而且总是用“原地逆序”这个限制——不开新数组不新建链表只靠改指针方向完成。迭代法的思路是把链表拆成三段当前节点cur、前驱pre、后继next。每次先把cur的后继保存下来再让cur指向前驱然后整体后移一格直到cur为NULL。代码很紧凑void reverseList(LinkList L) { LNode *pre NULL; LNode *cur L-next; while (cur ! NULL) { LNode *next cur-next; // 保存后继 cur-next pre; // 指针反转 pre cur; // 前驱前移 cur next; // 当前节点后移 } L-next pre; // 头节点指向新的首节点 }为什么当cur为NULL时pre正好是原链表的尾节点因为每轮结束时pre都会变成刚才处理过的cur循环结束后pre就是原链表最后一个节点此时它成为新链表的第一个节点。这一步很多人漏写头节点指向pre结果发现链表“没了”——因为头节点还指着原来的首节点而原来的首节点已经变成最末尾并且指向NULL了。用python做单链表逆序时思路完全一样只是对象引用代替了C的指针。我曾经用python跑过同一个逆序逻辑结构体换成class Node本质上没有差别。这个例子值得你手动在纸上推演一遍推完之后你对链表的信心会提升一大截。3. 双向链表实现比单链表多出来的那根指针3.1 双向链表的结构设计与优势双向链表节点在单链表基础上多了一个prior指针指向直接前驱。结构体定义这样写typedef struct DLNode { int data; struct DLNode *prior; struct DLNode *next; } DLNode, *DLinkList;多一个指针读写都多一步赋值空间开销上升但换来的是更对称的操作。单链表里你要删除某个节点p必须从头遍历找到p的前驱才能修改前驱的next如果只有p本身根本没法删。双向链表里p-prior直接就是前驱删除时能够同时修改前驱的next和后继的prior不需要从头找在已知节点位置的前提下插入和删除的复杂度都是O(1)。这就是为什么需要频繁在中间位置插入删除的场景会优先考虑双向链表哈希表的拉链、内存管理中的空闲块列表、浏览器页面的前进后退缓存都是双向链表的实际应用。3.2 插入操作的指针顺序陷阱双向链表的插入比单链表多了一步不仅要连接后继还要连接前驱。假设要在节点p之后插入节点s传统教学会给一段四步操作顺序尤其讲究s-next p-next; s-prior p; if (p-next ! NULL) p-next-prior s; p-next s;为什么第4步必须放在第5步之前因为第5步p-next s一旦执行p的原来后继就丢失了你再想通过p-next去访问它的prior就找不到了。这个顺序是双向链表里最常见的坑。我见过太多学生先写了p-next s然后回头想设置s-prior p-next-prior结果s-prior永远指向自己链表交叉成环一调试就懵。如果在p之前插入本质是“找到p-prior作为前插点”再按相同逻辑在它后面插入也可以用对称的写法。注意如果p是头节点或者链表只有p一个节点时p-prior为NULL操作前一定要判空不能对NULL解引用。这也是为什么我强调带头节点时头节点的prior通常直接置为NULL数据节点互连时优先考虑next单向prior只在需要回溯时使用。3.3 删除操作与双向链表的自删除特性删除节点p是双向链表最秀的操作不用遍历不用知道前驱是谁p自己就能把自己从链上摘下来。p-prior-next p-next; p-next-prior p-prior; free(p);这个自删除特性是单链表无论如何都做不到的。单链表删节点必须找前驱而双向链表只需要知道p本身改前驱的next和后继的prior就完成了删除。写这段代码的唯一风险在边界如果p是链表的第一个数据节点它的prior就是头节点头节点是存在的所以p-prior-next能安全操作但如果链表里只有这一个节点p-next为NULL执行p-next-prior时就会崩溃。正确的做法是加判断if (p-next ! NULL) { p-next-prior p-prior; } free(p);循环双链表是另一种常见变体头节点的prior指向尾节点尾节点的next指向头节点。这个结构里删除任意节点都不用考虑p-next是否为NULL因为最后一个节点处理完next仍然指向头节点整个环永远不断。但代价是判断“表空”和“表满”时要小心死循环。所以我的建议是先用带头节点的双链表把插入删除练熟再过渡到循环双链表后者大量出现在操作系统课程里也常被数据结构期末选做题青睐。4. 实操现场一个可复现的完整实验流程4.1 函数接口设计与模块划分很多实验课要求“在指定位置插入建立单链表”“单链表的清空”其实是在考察你能否把链表封装成一组自治的函数。我建议按照下面这套接口来组织代码initList初始化一个带头节点空表listInsert指定位置插入元素listDelete指定位置删除元素deleteNodeByValue按值删除匹配节点locateElem按值查找节点getElem按位序获取节点clearList清空所有数据节点但保留头节点destroyList销毁整张表释放所有节点printList遍历并打印把这十几个函数写完你的链表基本功基本就过关了。一个常见误解是clearList和destroyList的区别清空是只释放数据节点保留头节点下次还能继续插入销毁是连头节点一起释放链表彻底不可用。实验报告里经常要求写“单链表的清空”很多同学直接free了头节点后续测试全部崩溃。4.2 主流程实测记录与输出验证我写单链表实验时主函数习惯按这个流程自测初始化空表打印表长期望输出0。用尾插法插入5个元素打印全部节点验证顺序完全一致。在第3个位置插入一个99打印验证位置正确。删除第2个位置的元素打印验证后续节点前移。按值删除某个元素打印。用逆序函数反转链表打印验证顺序完全颠倒。清空链表打印表长期望输出0。我强烈建议你在学习阶段每一步都调用一次printList打印当前链表。看到输出和预期一致你才敢继续往下走。一旦输出不对劲立刻回看上一步操作而不是急着改代码。我调试链表时有一个习惯打印函数里特地输出每个节点的地址、数据和next指向地址这样能在输出里直接看到节点之间的前后关系比断点调试直观得多。用C语言写实验报告时这个“地址打印法”可以帮你一次性排查出指向错误和悬空指针。4.3 实验报告的写法要点实验报告在评分里常占20%到30%的分数很多学生吃亏不是代码没跑通而是报告写得像流水账。我批过不少实验报告总结出一套比较讨巧的套路需求分析部分明确写出输入输出格式比如“输入插入位置i从1开始和值val输出插入后的链表序列”。概要设计部分贴结构体定义和函数原型用文字说明每个函数做什么。详细设计部分贴核心代码配一段简单的指针变化图或者文字说明这能证明你是真懂而不是抄的。测试与运行部分贴三组有代表性的测试用例包含边界测试空表插入、尾部插入、删除首节点、逆序后再次插入。老师最反感的就是只有代码、没有测试、没有结果的报告。哪怕你设计得非常简单只要思路清晰、测试覆盖边界分数都不会低。5. 常见问题与避坑实录那些年我们踩过的指针坑5.1 问题速查表我整理了实操和答疑中最高频的链表错误做成了速查表方便你对照自查症状常见原因解决方案输出顺序和输入顺序相反用了头插法想得到正序改用尾插法或接受头插法是逆序建表插入后部分节点从链上“消失”先修改前驱next再改新节点next严格“先接新节点的next再接前驱的next”程序崩溃调试器指向freefree了栈上变量或重复free检查是否二次释放同一个指针free后置NULL遍历陷入死循环尾节点指向了自己或形成了环遍历循环条件写p ! NULL建表结束置尾节点next为NULL删除最后一个节点时崩溃对p-next-prior解引用但p-next为NULL先判断p-next是否为空双向链表插入后交叉“打结”四步指针赋值顺序错误先处理后继的prior再改前驱的next逆序后链表“断了”头节点没指向新的首节点循环结束后L-next pre5.2 排查思路与内存调试技巧链表debug最怕的是“逻辑上看着没问题一跑就崩”。我的排查顺序是先画图再插桩打印最后才开调试器。画图能解决一半问题打印能定位剩余问题调试器多用于确认malloc和free的配平。C语言里内存泄漏不像Python那样会给你异常提示free漏一个节点程序也能跑就是内存占用慢慢变大。为了养成好习惯我在每个malloc旁边都标注这个内存应该在哪一步free清空函数和销毁函数严格区分。用valgrind这类内存检测工具跑一遍如果报告出现“definitely lost”就说明有malloc没有对应free。还有一个很多人忽视的细节链表的“长度”和“第几个位置”经常差1。在有头节点的链表中第1个数据节点其实是头节点的next。所以遍历时从j1开始循环条件写成j i循环体里p后移最后p指向第i个节点。你要是把起点当成头节点本身的位序0就全错位了。我建议在做题时统一约定“位序从1开始计数”和教材保持一致考试不容易错。5.3 从“能运行”到“能考试”复习与提分的几点个人体会最后聊聊从“代码能跑”到“考试能拿分”这段路。我个人体会是链表的考试题拼的其实是“背图能力”。考场上写代码时间有限不可能从头推演所以你要在平时就把单链表的插入、删除、逆序、双链表的插入删除这五个核心操作的指针变化图画到熟。我每次带学生复习都让他们不看代码在白板上画指针图画完了再对照代码翻译。这个方法看着笨但效果非常好考场上遇到变体题时你只要画出图代码自然就出来了。408里常见变体包括两个有序链表合并成一个有序链表、删除链表中的重复值、找链表的中间节点。这些题的核心仍然是那几根指针怎么动。你只要把基础操作练成条件反射再叠加一点点逻辑就不怕题目怎么换了。距离考试还有时间的同学我建议每周把链表五大基本操作手写一遍写到不用思考就能写完的程度。到了考试前你就能腾出脑力去对付那些真正的难点。我这几年带过的学生里凡是动手画过指针图、把每个操作都跑通、再自己写一遍代码的链表部分基本不丢分。反过来只看书只看代码不实操的往往在链表上跌跟头。数据结构这东西眼睛会了手不会是常态手也练会了你才算真的跨过了这道门槛。

相关推荐

10个必看的AI Agent Harness开源项目:Awesome Harness Engineering 38个运行时与参考实现实战指南
10个必看的AI Agent Harness开源项目:Awesome Harness Engineering 38个运行时与参考实现实战指南

10个必看的AI Agent Harness开源项目:Awesome Harness Engineering 38个运行时与参考实现实战指南 【免费下载链接】awesome-harness-engineering 🛠️ Awesome tools & guides for harness engineering. 项目地址: https://gitcode.com/gh_mirror… · 2026/9/25 3:04:47

WeChatMsg 微信聊天记录导出完整教程:备份、检索、年度报告一次理清
WeChatMsg 微信聊天记录导出完整教程:备份、检索、年度报告一次理清

WeChatMsg 微信聊天记录导出完整教程:备份、检索、年度报告一次理清 【免费下载链接】WeChatMsg 提取微信聊天记录,将其导出成HTML、Word、CSV文档永久保存,对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Tren… · 2026/9/25 3:04:41

NgRx ComponentStore 初始化机制详解:构造函数初始化与惰性初始化(Lazy Initialization)
NgRx ComponentStore 初始化机制详解:构造函数初始化与惰性初始化(Lazy Initialization)

前端状态管理 【免费下载链接】platform Reactive State for Angular 项目地址: https://gitcode.com/gh_mirrors/pl/platform 点击查看 免费下载 本文基于 NgRx platform 仓库中 ComponentStore 指南的 Initialization 章节展开,系统讲解 ngrx/compone… · 2026/9/25 3:04:41

Breach 3靶场实战:从环境搭建到信息收集拿下第一个入口
Breach 3靶场实战:从环境搭建到信息收集拿下第一个入口

最近在复盘vulnhub上的Breach 3靶场,把它当成一次完整的实战前演练。说实话,这几年带人入门安全测试,最常被问到的问题不是“漏洞怎么利用”,而是“我连入口都找不到,接下来干什么”。DVWA和Pikachu这类靶场练的是漏洞… · 2026/9/25 3:32:27

机房管理系统源码包复现:反编译、重建数据库与运行排坑
机房管理系统源码包复现:反编译、重建数据库与运行排坑

简介:机房管理系统代码文件.zip是一套基于Java的机房管理信息系统源码包,面向需要开发或学习设备管理、上机统计、故障处理等场景的开发者与学生。压缩包共140个文件,体积约5MB,包含30个Java源文件、54个class编译文件、3个SQL脚本… · 2026/9/25 3:32:27

urql populateExchange 深度指南:用 @populate 指令自动填充 Mutation 查询字段
urql populateExchange 深度指南:用 @populate 指令自动填充 Mutation 查询字段

前端 【免费下载链接】urql The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow. 项目地址: https://gitcode.com/gh_mirrors/ur/urql 点击查看 免费下载 populateExchange 是 urql 生态中… · 2026/9/25 3:32:21

SQL Server 与 C 开发入门(Windows):ADO.NET、Entity Framework 与列存储索引实战指南
SQL Server 与 C 开发入门(Windows):ADO.NET、Entity Framework 与列存储索引实战指南

示例工程数据库教程后端 【免费下载链接】sql-server-samples Azure Data SQL Samples - Official Microsoft GitHub Repository containing code samples for SQL Server, Azure SQL, Azure Synapse, and Azure SQL Edge 项目地址: https://gitcode.com/gh_mirrors… · 2026/9/25 3:32:21

Swagger Codegen 生成的 Java 客户端 UserApi 实战指南:基于 okhttp4-gson-parcelableModel 的 Petstore 用户接口
Swagger Codegen 生成的 Java 客户端 UserApi 实战指南:基于 okhttp4-gson-parcelableModel 的 Petstore 用户接口

开发工具代码生成API设计 【免费下载链接】swagger-codegen swagger-codegen contains a template-driven engine to generate documentation, API clients and server stubs in different languages by parsing your OpenAPI / Swagger definition. 项目地址: http… · 2026/9/25 3:32:21

全域智能管控平台权限管理:RBAC模型落地与安全管控实践
全域智能管控平台权限管理:RBAC模型落地与安全管控实践

聊到权限管理,很多人第一反应就是"给谁开通什么功能",似乎建个用户列表再打个勾就完事了。但真正做过安防平台、物联网管控平台或者企业内部中台的人都会明白,权限管理从来不是界面交互问题,而是整个系统的安全底座。尤… · 2026/9/25 3:32:21

数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)
数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码