3.链表:用指针串联的动态数据结构
一、什么是链表链表Linked List是一种非连续、非顺序的线性数据结构它通过指针将一组零散的内存块串联起来每个内存块被称为节点Node。简单来说链表就像一串 “糖葫芦”每个 “山楂” 是一个节点包含数据和下一个节点的地址通过 “竹签”指针将所有节点串联起来形成一个完整的链条。二、链表的核心结构节点每个链表节点由两部分组成数据域存储节点的具体数据指针域存储下一个节点的地址。对应的 C 语言代码定义如下// 链表节点结构体 typedef struct Node { int data; // 数据域存储节点数据 struct Node* next; // 指针域指向next节点 } Node;三、链表的基本操作1. 头插法在链表头部插入节点头插法是将新节点插入到链表的最前端步骤如下让新节点的next指向当前的头节点更新头节点为新节点。代码实现// 头插法在链表头部插入新节点 void headInsert(Node** head, int value) { // 1. 创建新节点 Node* newNode (Node*)malloc(sizeof(Node)); newNode-data value; // 2. 新节点的next指向当前头节点 newNode-next *head; // 3. 更新头节点为新节点 *head newNode; }2. 尾插法在链表尾部插入节点尾插法是将新节点插入到链表的最后端步骤如下遍历链表找到最后一个节点让最后一个节点的next指向新节点新节点的next指向NULL。代码实现// 尾插法在链表尾部插入新节点 void tailInsert(Node** head, int value) { // 1. 创建新节点 Node* newNode (Node*)malloc(sizeof(Node)); newNode-data value; newNode-next NULL; // 2. 如果链表为空直接让头节点指向新节点 if (*head NULL) { *head newNode; return; } // 3. 遍历找到最后一个节点 Node* p *head; while (p-next ! NULL) { p p-next; } // 4. 最后一个节点的next指向新节点 p-next newNode; }3. 遍历链表遍历链表是从头部开始依次访问每个节点直到遇到NULL结束时间复杂度为O(n)。代码实现// 遍历链表并打印所有节点 void traverseList(Node* head) { Node* p head; while (p ! NULL) { printf(%d , p-data); p p-next; } printf(\n); }4. 指定位置插入节点在指定位置插入节点需要先找到该位置的前驱节点步骤如下遍历找到前驱节点p让新节点的next指向p的next让p的next指向新节点。代码实现// 指定位置插入节点在第pos个位置插入新节点pos从0开始 void insertAt(Node** head, int pos, int value) { // 1. 处理头插情况pos0 if (pos 0) { headInsert(head, value); return; } // 2. 找到前驱节点 Node* p *head; for (int i 0; i pos - 1 p ! NULL; i) { p p-next; } // 3. 如果前驱节点不存在直接返回 if (p NULL) { printf(插入位置无效\n); return; } // 4. 创建新节点并插入 Node* newNode (Node*)malloc(sizeof(Node)); newNode-data value; newNode-next p-next; p-next newNode; }5. 指定位置删除节点删除指定位置的节点需要先找到该位置的前驱节点步骤如下遍历找到前驱节点p保存要删除的节点tmp p-next让p的next指向tmp的next释放tmp的内存。代码实现// 指定位置删除节点删除第pos个位置的节点pos从0开始 void deleteAt(Node** head, int pos) { // 1. 处理头删情况pos0 if (pos 0 *head ! NULL) { Node* tmp *head; *head (*head)-next; free(tmp); return; } // 2. 找到前驱节点 Node* p *head; for (int i 0; i pos - 1 p ! NULL p-next ! NULL; i) { p p-next; } // 3. 如果前驱节点或要删除的节点不存在直接返回 if (p NULL || p-next NULL) { printf(删除位置无效\n); return; } // 4. 删除节点并释放内存 Node* tmp p-next; p-next tmp-next; free(tmp); }6. 头删与尾删 头删直接删除头节点更新头节点为下一个节点 尾删遍历找到倒数第二个节点让其next指向NULL释放最后一个节点。 代码实现// 头删删除链表的第一个节点 void headDelete(Node** head) { if (*head NULL) { printf(链表为空无法头删\n); return; } Node* tmp *head; *head (*head)-next; free(tmp); } // 尾删删除链表的最后一个节点 void tailDelete(Node** head) { if (*head NULL) { printf(链表为空无法尾删\n); return; } // 1. 如果只有一个节点直接删除 if ((*head)-next NULL) { free(*head); *head NULL; return; } // 2. 找到倒数第二个节点 Node* p *head; while (p-next-next ! NULL) { p p-next; } // 3. 删除最后一个节点 Node* tmp p-next; p-next NULL; free(tmp); }四、链表 vs 数组链表和数组是两种最常用的线性数据结构它们的区别如下五、链表的适用场景根据链表和数组的特性它们的适用场景如下链表适用场景数据量不确定需要动态扩容频繁进行插入、删除操作不需要频繁随机访问。数组适用场景数据量固定需要频繁随机访问内存连续需要高访问效率。六、完整代码示例#include stdio.h #include stdlib.h // 链表节点结构体 typedef struct Node { int data; struct Node* next; } Node; // 头插法 void headInsert(Node** head, int value) { Node* newNode (Node*)malloc(sizeof(Node)); newNode-data value; newNode-next *head; *head newNode; } // 尾插法 void tailInsert(Node** head, int value) { Node* newNode (Node*)malloc(sizeof(Node)); newNode-data value; newNode-next NULL; if (*head NULL) { *head newNode; return; } Node* p *head; while (p-next ! NULL) { p p-next; } p-next newNode; } // 遍历链表 void traverseList(Node* head) { Node* p head; while (p ! NULL) { printf(%d , p-data); p p-next; } printf(\n); } // 指定位置插入 void insertAt(Node** head, int pos, int value) { if (pos 0) { headInsert(head, value); return; } Node* p *head; for (int i 0; i pos - 1 p ! NULL; i) { p p-next; } if (p NULL) { printf(插入位置无效\n); return; } Node* newNode (Node*)malloc(sizeof(Node)); newNode-data value; newNode-next p-next; p-next newNode; } // 指定位置删除 void deleteAt(Node** head, int pos) { if (pos 0 *head ! NULL) { Node* tmp *head; *head (*head)-next; free(tmp); return; } Node* p *head; for (int i 0; i pos - 1 p ! NULL p-next ! NULL; i) { p p-next; } if (p NULL || p-next NULL) { printf(删除位置无效\n); return; } Node* tmp p-next; p-next tmp-next; free(tmp); } // 头删 void headDelete(Node** head) { if (*head NULL) { printf(链表为空无法头删\n); return; } Node* tmp *head; *head (*head)-next; free(tmp); } // 尾删 void tailDelete(Node** head) { if (*head NULL) { printf(链表为空无法尾删\n); return; } if ((*head)-next NULL) { free(*head); *head NULL; return; } Node* p *head; while (p-next-next ! NULL) { p p-next; } Node* tmp p-next; p-next NULL; free(tmp); } // 释放链表内存 void freeList(Node** head) { Node* p *head; while (p ! NULL) { Node* tmp p; p p-next; free(tmp); } *head NULL; } int main() { Node* head NULL; // 测试头插法 headInsert(head, 3); headInsert(head, 5); headInsert(head, 7); printf(头插后链表); traverseList(head); // 输出7 5 3 // 测试尾插法 tailInsert(head, 9); printf(尾插后链表); traverseList(head); // 输出7 5 3 9 // 测试指定位置插入 insertAt(head, 2, 6); printf(在位置2插入6后); traverseList(head); // 输出7 5 6 3 9 // 测试指定位置删除 deleteAt(head, 2); printf(删除位置2的节点后); traverseList(head); // 输出7 5 3 9 // 测试头删 headDelete(head); printf(头删后链表); traverseList(head); // 输出5 3 9 // 测试尾删 tailDelete(head); printf(尾删后链表); traverseList(head); // 输出5 3 // 释放链表内存 freeList(head); printf(释放内存后链表为空%s\n, head NULL ? 是 : 否); // 输出是 return 0; }七、总结链表是一种动态、灵活的线性数据结构它通过指针将节点串联起来支持高效的插入和删除操作但随机访问效率较低。在实际开发中链表常用于动态数组如ArrayList的底层实现队列、栈等数据结构的实现操作系统的内存管理图和树的遍历等。希望这篇文章能帮助你深入理解链表的原理和实现

相关新闻

Claude首破黎曼猜想新纪录

Claude首破黎曼猜想新纪录

黎曼猜想,差点被攻克了! 今天,Anthropic重磅发文,让内部研究版Claude去证明「黎曼猜想」,结果失败了。 但Claude却在这一世纪难题,取得了重大突破—— 它成功将符合猜想的黎曼Zeta函数零点比例下限&…

2026/10/13 4:22:52 阅读更多 →
Figma界面还是英文?FigmaCN中文插件完整指南:4383个术语几分钟全搞定

Figma界面还是英文?FigmaCN中文插件完整指南:4383个术语几分钟全搞定

Figma界面还是英文?FigmaCN中文插件完整指南:4383个术语几分钟全搞定 【免费下载链接】figmaCN 中文 Figma 插件,设计师人工翻译校验 项目地址: https://gitcode.com/gh_mirrors/fi/figmaCN 你是否有过这样的体验:打开 Fig…

2026/10/7 3:31:33 阅读更多 →
IDEA中Git Pull与Update Project核心区别与最佳实践指南

IDEA中Git Pull与Update Project核心区别与最佳实践指南

1. 项目概述:从一次“误操作”引发的思考如果你和我一样,长期使用 IntelliJ IDEA 作为主力开发工具,并且团队代码托管在 Git 上,那么你一定对 IDEA 的 VCS 菜单里那两个功能不陌生:Git Pull和Update Project。我敢打赌…

2026/10/7 4:18:44 阅读更多 →

最新新闻

缠中说禅博客评论数据清洗与全文检索实战:从HTML到SQLite FTS5

缠中说禅博客评论数据清洗与全文检索实战:从HTML到SQLite FTS5

简介:这份资源是缠中说禅博客的完整归档压缩包,面向对缠论与股票、期货技术分析感兴趣的投资者及研究者,尤其适合希望系统研读原文、结合评论理解理论脉络的中高级学习者。包内共约2000个文件,以html网页存档为主体,辅…

2026/10/13 4:23:05 阅读更多 →
柔性上料盘选型指南:从技术原理到市场趋势与避坑实战

柔性上料盘选型指南:从技术原理到市场趋势与避坑实战

简介:这是一份由QYResearch出品的《全球与中国柔性上料盘市场现状及未来发展趋势(2024版)》行业研究报告,面向智能制造、工业自动化领域的市场研究人员、企业战略决策者及投资分析师,可用于掌握柔性上料盘细分赛道的市…

2026/10/13 4:23:05 阅读更多 →
iOS上架全流程:AppUploader与Xcode证书配置实战指南

iOS上架全流程:AppUploader与Xcode证书配置实战指南

做了几年 iOS 发包,我最深的感触是:第一次把项目跑到上架阶段时,最让人血压升高的往往不是崩溃的代码,而是那一堆证书页面、描述文件选项、以及构建导出的各种弹窗。很多开发者把精力放在功能开发上,结果到了最后一步才…

2026/10/13 4:23:05 阅读更多 →
多语言微服务消息幂等与重试机制的最佳实践

多语言微服务消息幂等与重试机制的最佳实践

做过多语言微服务的人应该都有体会:消息幂等性和重试机制单独拎出来写都能出一本书,可一旦把它们放进多语言环境里,问题就会变得非常拧巴。Java服务写好的幂等逻辑,换到Go服务里可能就跑不通;消息队列里明明没重复&…

2026/10/13 4:23:05 阅读更多 →
安立MT8821C综测仪实操指南:从手机射频测试到产线自动化

安立MT8821C综测仪实操指南:从手机射频测试到产线自动化

手机从产线下来之前,总有一台仪器要在背后默默把关——安立 MT8821C 无线电通信分析仪,也就是射频工程师口里的“综测仪”。这台设备既能当基站把手机拉进网络,又能直接掰开射频链路看发射和接收的每一项指标。做手机研发、产线测试或者终端维…

2026/10/13 4:23:05 阅读更多 →
手写通用状态机,终结订单状态流转的if-else噩梦

手写通用状态机,终结订单状态流转的if-else噩梦

接手过几个订单系统的同学应该都有同感:状态流转这块代码,刚开始写if-else还挺顺手的,等业务跑起来、需求迭代几个月之后,就会变成一个到处打补丁的“面条锅”。今天我用订单场景做例子,从零手写一个通用状态机&#x…

2026/10/13 4:22:04 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 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/12 21:05:09 阅读更多 →
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/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练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/12 18:11:17 阅读更多 →