C语言链表完全指南:从单节点到链表管理

news2026/5/7 2:35:02
引言在数据结构的学习中我们首先学习了顺序表数组。顺序表虽然访问速度快但插入和删除操作需要移动大量元素效率较低。此外顺序表的大小固定扩容需要重新分配内存并拷贝数据。链表解决了这些问题。链表中的元素在内存中不连续存储通过指针连接形成逻辑上的线性结构。今天我们将从零开始实现一个完整的带头结点的单向链表涵盖初始化、插入、删除、遍历等核心操作。第一部分链表的基本概念一、什么是链表链表是一种物理存储单元上非连续、非顺序的线性结构。数据元素之间的逻辑关系通过指针链接实现。链表的分类分类标准类型说明方向单向链表每个节点只有一个指向后继的指针双向链表每个节点有前驱和后继两个指针是否循环非循环链表尾节点指针指向NULL循环链表尾节点指针指向头节点是否有头结点带头结点头结点不存储数据简化操作不带头结点第一个节点就存储数据本文实现的是带头结点的单向非循环链表。二、带头结点的优势特点不带头结点带头结点空表判断head NULLhead-next NULL头插操作需要修改头指针操作统一不修改头指针删除头节点需要修改头指针操作统一代码复杂度较高较低第二部分数据结构设计一、节点结构体typedef int ElemType; // 链表存储的数据类型 // 节点结构体 typedef struct ListNode { ElemType data; // 数据域存储实际数据 struct ListNode* next; // 指针域指向下一个节点 } ListNode;节点结构体大小┌─────────────────────────────────────────────┐ │ ListNode 节点 │ ├─────────────────────────────────────────────┤ │ data (4字节) │ next (8字节64位系统) │ └─────────────────────────────────────────────┘ 总大小12字节可能内存对齐后为16字节二、链表管理结构体// 链表管理结构体 struct LinkList { ListNode* head; // 指向头结点不存储数据 size_t cursize; // 当前链表中的元素个数 };设计意图head始终指向头结点头结点的next指向第一个实际存储数据的节点cursize记录链表长度避免每次遍历计算第三部分链表的基本操作一、初始化原理创建头结点让head指向它头结点的next指向NULL。void InitLinkList(struct LinkList* ps) { assert(ps ! NULL); // 分配头结点内存 ListNode* p (ListNode*)malloc(sizeof(ListNode)); if (p NULL) return; p-data 0; // 头结点数据域无用可任意赋值 p-next NULL; // 初始时没有实际节点 ps-head p; ps-cursize 0; }初始化后的内存布局二、创建新节点辅助函数ListNode* BuyNode(ElemType val) { ListNode* p (ListNode*)malloc(sizeof(ListNode)); if (p NULL) return p; p-data val; p-next NULL; return p; }三、遍历与打印void PrintLinkList(struct LinkList* ps) { assert(ps ! NULL); // 从第一个实际节点开始遍历跳过带头结点 for (ListNode* p ps-head-next; p ! NULL; p p-next) { printf(%d , p-data); } printf(\n); }第四部分插入操作一、尾插在尾部插入原理遍历到尾节点将新节点链接到尾部。// 时间复杂度O(n) bool InsertBack(struct LinkList* ps, ElemType val) { assert(ps ! NULL); ListNode* p BuyNode(val); if (p NULL) return false; // 找到尾节点 ListNode* q ps-head; while (q-next ! NULL) { q q-next; } // 链接新节点 p-next q-next; // 等价于 p-next NULL q-next p; ps-cursize; return true; }示意图插入前 : head → ┌───┐ ┌───┐ ┌───┐ │10 │→ │20 │→ │30 │→ NULL └───┘ └───┘ └───┘ 插入val40后 ┌───┐ ┌───┐ ┌───┐ ┌───┐ │10 │→ │20 │→ │30 │→ │40 │→ NULL └───┘ └───┘ └───┘ └───┘二、头插在头部插入原理新节点的next指向原来的第一个节点头结点指向新节点。// 时间复杂度O(1) bool InsertFront(struct LinkList* ps, ElemType val) { assert(ps ! NULL); ListNode* p BuyNode(val); if (p NULL) return false; p-next ps-head-next; // 新节点指向原第一个节点 ps-head-next p; // 头结点指向新节点 ps-cursize; return true; }示意图三、按位置插入原理找到第pos-1个节点在其后插入新节点。// 时间复杂度O(n) bool InsertPos(struct LinkList* ps, ElemType val, int pos) { assert(ps ! NULL); // 位置有效性检查1 ≤ pos ≤ cursize1 if (pos 1 || pos ps-cursize 1) return false; ListNode* p BuyNode(val); if (p NULL) return false; // 找到第 pos-1 个节点 ListNode* q ps-head; for (int i 0; i pos - 1; i) { q q-next; } // 插入新节点 p-next q-next; q-next p; ps-cursize; return true; }第五部分删除操作一、尾删删除尾部节点原理找到倒数第二个节点将其next设为NULL释放尾节点。// 时间复杂度O(n) bool PopBack(struct LinkList* ps) { assert(ps ! NULL); if (ps-cursize 0) return false; // 空链表 // 找到倒数第二个节点 ListNode* q ps-head; while (q-next-next ! NULL) { q q-next; } // 释放尾节点 ListNode* p q-next; q-next p-next; // 即 q-next NULL free(p); p NULL; ps-cursize--; return true; }二、头删删除头部节点原理头结点绕过第一个节点指向第二个节点释放第一个节点。// 时间复杂度O(1) bool PopFront(struct LinkList* ps) { assert(ps ! NULL); if (ps-cursize 0) return false; ListNode* p ps-head-next; // 要删除的节点 ps-head-next p-next; // 头结点指向第二个节点 free(p); p NULL; ps-cursize--; return true; }三、按位置删除// 时间复杂度O(n) bool PopPos(struct LinkList* ps, int pos) { assert(ps ! NULL); if (ps-cursize 0) return false; if (pos 1 || pos ps-cursize) return false; // 找到第 pos-1 个节点 ListNode* q ps-head; for (int i 0; i pos - 1; i) { q q-next; } // 删除第 pos 个节点 ListNode* p q-next; q-next p-next; free(p); p NULL; ps-cursize--; return true; }第六部分完整测试代码#include List.h #include stdio.h int main() { struct LinkList list; // 1. 初始化 InitLinkList(list); printf(初始化后元素个数%zu\n, list.cursize); // 2. 头插 InsertFront(list, 10); InsertFront(list, 20); InsertFront(list, 30); printf(头插后); PrintLinkList(list); // 30 20 10 // 3. 尾插 InsertBack(list, 40); InsertBack(list, 50); printf(尾插后); PrintLinkList(list); // 30 20 10 40 50 // 4. 按位置插入 InsertPos(list, 100, 3); // 在第3个位置插入100 printf(位置插入后); PrintLinkList(list); // 30 20 100 10 40 50 // 5. 头删 PopFront(list); printf(头删后); PrintLinkList(list); // 20 100 10 40 50 // 6. 尾删 PopBack(list); printf(尾删后); PrintLinkList(list); // 20 100 10 40 // 7. 按位置删除 PopPos(list, 2); printf(删除位置2后); PrintLinkList(list); // 20 10 40 return 0; }第七部分顺序表 vs 链表操作顺序表链表说明按下标访问O(1)O(n)链表需要遍历头部插入O(n)O(1)链表只需改指针头部删除O(n)O(1)链表只需改指针尾部插入O(1)扩容时O(n)O(n)链表需遍历到尾中间插入O(n)O(n)都需要定位内存占用较少较多链表有指针开销缓存友好好差顺序表内存连续选择建议频繁按下标访问 → 顺序表频繁头插/头删 → 链表频繁尾插 → 顺序表或维护尾指针的链表大小频繁变化 → 链表第八部分常见错误与注意事项一、指针操作错误// ❌ 错误p未初始化就修改next ListNode* p; p-next NULL; // ✅ 正确先分配内存 ListNode* p (ListNode*)malloc(sizeof(ListNode)); p-next NULL;二、内存泄漏// ❌ 错误删除节点后未释放内存 q-next p-next; // p 的内存泄漏了 // ✅ 正确释放内存 q-next p-next; free(p); p NULL;三、空指针解引用// ❌ 错误未检查链表是否为空 bool PopFront(struct LinkList* ps) { ListNode* p ps-head-next; ps-head-next p-next; // 如果p是NULL这里崩溃 free(p); return true; } // ✅ 正确检查空链表 bool PopFront(struct LinkList* ps) { if (ps-cursize 0) return false; // ... 正常删除逻辑 }总结一、链表操作复杂度总结操作时间复杂度代码关键点初始化O(1)创建头结点尾插O(n)遍历到尾节点头插O(1)新节点指向原第一个节点中间插入O(n)定位到前驱节点尾删O(n)找到倒数第二个节点头删O(1)头结点指向第二个节点中间删除O(n)定位到前驱节点遍历O(n)从头结点下一个开始二、带头结点的优势场景不带头结点带头结点空表判断head NULLhead-next NULL头插操作需要修改头指针操作统一删除头节点需要修改头指针操作统一三、核心函数速查函数功能关键操作InitLinkList初始化创建头结点BuyNode创建节点malloc 赋值InsertBack尾插遍历到尾 链接InsertFront头插头结点后插入InsertPos位置插入找前驱 链接PopBack尾删找倒数第二个 释放PopFront头删头结点绕过第一个PopPos位置删除找前驱 释放链表是数据结构中非常重要的基础结构。本文实现了带头结点的单向链表涵盖以下知识点节点结构包含数据域和指针域头结点简化插入删除操作插入操作头插、尾插、按位置插入删除操作头删、尾删、按位置删除内存管理malloc分配、free释放复杂度分析理解各操作的时间复杂度学习建议画图理解指针操作不要只靠想象注意边界条件空链表、单节点链表插入和删除时确保指针指向正确释放内存后将指针置NULL防止野指针

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2590109.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…