数据结构篇(四):线性表——链表——双链表
前言上一篇讲了单链表单链表虽然解决了顺序表头部插入删除效率低的问题但它自身也有痛点不支持逆向遍历尾插尾删效率是O(N)很多操作都要先找前驱节点。为了解决这些问题带头双向循环链表登场了——它是链表结构中最复杂但实际工程中最常用的一种C STL中的list底层就是它。本文将系统讲解它的结构与实现。一、什么是双链表双链表Doubly Linked List的每个节点除了数据域还有两个指针域一个指向前一个节点prev一个指向后一个节点next。本文实现的是带头双向循环链表它有三个关键特征带头有一个不存储有效数据的哨兵头节点哨兵位头节点永远存在即使链表为空双向每个节点既能找到前驱也能找到后继循环最后一个节点的next指向头节点头节点的prev指向最后一个节点形成一个环。带头双向循环链表看起来结构复杂但正因为头节点永远存在所以插入删除时不需要对链表是否为空做特殊判断代码反而比单链表更简单统一这是它的一大优势。二、双链表的结构定义​typedef int LTDataType; typedef struct ListNode { LTDataType data; // 数据域 struct ListNode* prev; // 指向前一个节点 struct ListNode* next; // 指向后一个节点 } ListNode;由于是带头循环结构整个链表只需要一个头节点指针即可代表不再需要像单链表那样用二级指针传参。三、双链表的基本操作3.1 创建新节点ListNode* BuyListNode(LTDataType x) { ListNode* newNode (ListNode*)malloc(sizeof(ListNode)); if (newNode NULL) { perror(malloc fail); exit(-1); } newNode-data x; newNode-prev NULL; newNode-next NULL; return newNode; }3.2 初始化创建带头节点的空链表ListNode* ListInit() { ListNode* head BuyListNode(0); // 哨兵位data值无意义 head-next head; head-prev head; return head; }3.3 头插void ListPushFront(ListNode* phead, LTDataType x) { assert(phead ! NULL); ListNode* newNode BuyListNode(x); ListNode* first phead-next; // 新节点插入头节点和原第一个节点之间 phead-next newNode; newNode-prev phead; newNode-next first; first-prev newNode; }3.4 头删void ListPopFront(ListNode* phead) { assert(phead ! NULL); assert(phead-next ! phead); // 链表不能为空 ListNode* first phead-next; ListNode* second first-next; phead-next second; second-prev phead; free(first); }3.5 尾插得益于循环结构phead-prev永远指向最后一个节点所以尾插不需要遍历直接是O(1)——这是双链表相比单链表最大的效率提升。void ListPushBack(ListNode* phead, LTDataType x) { assert(phead ! NULL); ListNode* newNode BuyListNode(x); ListNode* last phead-prev; last-next newNode; newNode-prev last; newNode-next phead; phead-prev newNode; }3.6 尾删void ListPopBack(ListNode* phead) { assert(phead ! NULL); assert(phead-next ! phead); ListNode* last phead-prev; ListNode* newLast last-prev; newLast-next phead; phead-prev newLast; free(last); }3.7 查找ListNode* ListFind(ListNode* phead, LTDataType x) { assert(phead ! NULL); ListNode* cur phead-next; while (cur ! phead) { if (cur-data x) { return cur; } cur cur-next; } return NULL; // 没找到 }3.8 在指定位置之前/之后插入有了prev指针双链表可以在O(1)时间内完成任意已知位置的插入不再需要像单链表那样遍历找前驱。// 在pos之前插入xO(1) void ListInsert(ListNode* pos, LTDataType x) { assert(pos ! NULL); ListNode* newNode BuyListNode(x); ListNode* prev pos-prev; prev-next newNode; newNode-prev prev; newNode-next pos; pos-prev newNode; } // 在pos之后插入xO(1) void ListInsertAfter(ListNode* pos, LTDataType x) { assert(pos ! NULL); ListNode* newNode BuyListNode(x); ListNode* next pos-next; pos-next newNode; newNode-prev pos; newNode-next next; next-prev newNode; }3.9 删除指定位置节点void ListErase(ListNode* pos) { assert(pos ! NULL); ListNode* prev pos-prev; ListNode* next pos-next; prev-next next; next-prev prev; free(pos); }3.10 打印链表void ListPrint(ListNode* phead) { assert(phead ! NULL); printf(head - ); ListNode* cur phead-next; while (cur ! phead) { printf(%d - , cur-data); cur cur-next; } printf(head\n); }3.11 销毁链表void ListDestroy(ListNode* phead) { assert(phead ! NULL); ListNode* cur phead-next; while (cur ! phead) { ListNode* next cur-next; free(cur); cur next; } free(phead); // 别忘了释放头节点自己 }四、完整测试代码int main() { ListNode* plist ListInit(); ListPushBack(plist, 1); ListPushBack(plist, 2); ListPushBack(plist, 3); ListPrint(plist); // head - 1 - 2 - 3 - head ListPushFront(plist, 0); ListPrint(plist); // head - 0 - 1 - 2 - 3 - head ListNode* pos ListFind(plist, 2); if (pos) { ListInsert(pos, 100); } ListPrint(plist); // head - 0 - 1 - 100 - 2 - 3 - head ListPopFront(plist); ListPopBack(plist); ListPrint(plist); // head - 1 - 100 - 2 - head ListDestroy(plist); return 0; }五、时间复杂度分析操作时间复杂度说明头插/头删O(1)直接操作头节点尾插/尾删O(1)有prev指针无需遍历指定位置插入/删除O(1)已知位置即可直接操作查找O(N)仍需遍历随机访问下标O(N)不支持真正的随机访问可以看到除了查找和随机访问双链表的增删操作全部是O(1)这是它相比单链表和顺序表最大的优势。六、双链表 vs 单链表 vs 顺序表特性顺序表单链表双链表带头循环存储方式物理地址连续物理地址不连续物理地址不连续随机访问O(1)O(N)O(N)头部插入删除O(N)O(1)O(1)尾部插入删除O(1)均摊O(N)O(1)任意位置插入删除O(N)O(N)需找前驱O(1)已知位置逆向遍历支持不支持支持空指针判断不需要需要频繁判断头节点恒存在几乎不需要空间开销可能有扩容冗余一个指针两个指针可以看出双链表几乎在所有增删操作上都做到了O(1)代价是每个节点多了一个prev指针的空间开销空间换时间以及实现相对更复杂。这也是为什么STL选择用带头双向循环链表实现list——用少量的额外空间换取了全方位的高效增删。七、总结双链表尤其是带头双向循环链表是链表结构的完全体因为带头插入删除不用特判链表是否为空因为双向可以O(1)找到任意节点的前驱也支持逆向遍历因为循环phead-prev天然就是尾节点尾插尾删也能做到O(1)。理解了双链表的实现原理再回头看STL的list、unordered_map的哈希桶等结构会更加得心应手。链表和顺序表是线性表的两种典型实现方式二者各有优劣没有绝对的孰优孰劣需要根据实际的业务场景是否频繁随机访问、是否频繁增删、数据规模是否已知来做选择。理解透单链表的指针操作尤其是二级指针的使用、边界条件的处理是后续学习双向链表、栈、队列乃至STL中list容器的重要基础。如果这篇文章对你有帮助欢迎点赞收藏后续会继续更新栈、队列、二叉树等数据结构内容

相关新闻

手把手带你用AI重构电商系统:从传统Spring Boot迁移到LLM-Native全栈架构(含性能对比:TPS提升3.2倍)

手把手带你用AI重构电商系统:从传统Spring Boot迁移到LLM-Native全栈架构(含性能对比:TPS提升3.2倍)

更多请点击: https://kaifayun.com 第一章:手把手带你用AI重构电商系统:从传统Spring Boot迁移到LLM-Native全栈架构(含性能对比:TPS提升3.2倍) 传统电商后端长期依赖硬编码业务规则与静态API契约&#xf…

2026/10/16 9:27:36 阅读更多 →
当 Claude 思考链注入 Qwen3.5-9B:轻量化模型兼具推理质感

当 Claude 思考链注入 Qwen3.5-9B:轻量化模型兼具推理质感

部署过本地大模型的人,经常遇到这样的困境:7B 的小模型聊天尚可,一遇到复杂推导就逻辑断裂;30B 以上的倒是聪明,但显存堪忧。更无解的是,当你想讨论网络安全协议或生物医药机制时,很多模型的技术…

2026/10/15 7:03:44 阅读更多 →
免费游戏下载安全指南:从病毒清除到系统防护完整方案

免费游戏下载安全指南:从病毒清除到系统防护完整方案

最近不少朋友在下载免费游戏时遇到了麻烦——电脑中毒不说,还莫名其妙进入了奇怪的"八尺大人"世界。这听起来像都市传说,但背后反映的是当前免费游戏分发渠道的安全隐患问题。今天我们就来彻底拆解这类安全事件的成因,并给出从预防…

2026/10/15 7:03:51 阅读更多 →

最新新闻

嵌入式C语言实现多项式拟合polyfit:资源受限场景下的高鲁棒性算法

嵌入式C语言实现多项式拟合polyfit:资源受限场景下的高鲁棒性算法

简介:本资源是一份面向C语言初学者与嵌入式/Linux开发者的数值计算实践代码包,聚焦多项式拟合这一经典数据建模方法,解决在无高级语言环境(如Python/Matlab)时,如何用纯C实现最小二乘法拟合并评估模型的问题…

2026/10/16 21:18:39 阅读更多 →
ThreadX资源体系深度解析:从内核裁剪到运行时健康监控

ThreadX资源体系深度解析:从内核裁剪到运行时健康监控

简介:本资源是面向嵌入式开发工程师与RTOS学习者的ThreadX实时操作系统全栈学习包,聚焦工业控制、汽车电子、医疗设备等对实时性要求严苛的领域,助力初学者系统入门与进阶开发者深度定制优化。压缩包含134个文件,以100个C源文件和…

2026/10/16 21:18:39 阅读更多 →
AD9361寄存器配置本质:射频状态机与时序约束解析

AD9361寄存器配置本质:射频状态机与时序约束解析

简介:本资源是一份面向嵌入式射频开发工程师与SDR(软件定义无线电)初学者的AD9361寄存器级配置实战指南,聚焦高频通信系统中收发器底层驱动调试这一关键难点。内容覆盖参考时钟设定、LVDS接口配置、采样率与带宽协同调整、AGC/MGC…

2026/10/16 21:18:39 阅读更多 →
SOEM源码解析:从EtherCAT主站协议栈到Linux实时运行实践

SOEM源码解析:从EtherCAT主站协议栈到Linux实时运行实践

简介:SOEM库源码是一套基于EtherCAT主站协议的开源实现,面向工业自动化、运动控制和实时通信领域的开发者,可帮助构建主站并进行设备扫描、配置、状态监控及数据读写。资源压缩包共141个文件,以c源文件、h头文件为主体&#xff0c…

2026/10/16 21:18:39 阅读更多 →
TDC-GP22时间数字转换芯片详解:从测量原理到例程移植避坑指南

TDC-GP22时间数字转换芯片详解:从测量原理到例程移植避坑指南

简介:面向单片机与嵌入式硬件开发者的TDC-GP22芯片例程包,聚焦MSP430超低功耗单片机与高精度时间测量芯片的协同应用,可支撑超声水表流量计算、超声波测距、脉冲测速等典型场景,也适合作为高校课程设计、毕业设计或产品原型的基础…

2026/10/16 21:18:39 阅读更多 →
数组差异识别:带顺序保障的集合差集实现

数组差异识别:带顺序保障的集合差集实现

1. 这道题到底在考什么:从“找不同”到工程级数据比对思维“找出不是两个数组共有的元素”——光看标题,很多人第一反应是“这不就是个去重取差集的小练习吗?用个set不就完事了?”但如果你真这么想,说明你还没摸到PTA这…

2026/10/16 21:17:38 阅读更多 →

日新闻

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

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

2026/10/16 0:00:05 阅读更多 →
信号与系统频域分析作业解题指南

信号与系统频域分析作业解题指南

1. 作业背景与核心要求解析信号与系统作为电子信息类专业的核心基础课,其作业设计往往直接关联课程的核心知识框架。2026年春季第六次作业的布置,通常对应着课程进展到频域分析的关键阶段。根据常规教学进度推测,本次作业可能涉及以下核心内容…

2026/10/16 0:00:05 阅读更多 →
Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

1. 从一个"四不像"的练手项目说起如果你已经看完了Django的官方教程前几章,跟着敲完了投票应用,大概会有一种"我好像会了,又好像什么都不会"的割裂感。教程里每个知识点都是拆开的——这一章讲模型,下一章讲视…

2026/10/16 0:00:05 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/16 14:23:36 阅读更多 →