news 2026/8/28 7:54:51

数据结构精讲:单链表的操作

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
数据结构精讲:单链表的操作

一.链表基础(单链表)

1. 物理结构与逻辑结构

  • 逻辑结构:线性结构,元素一个接一个排列。

  • 物理结构非连续、非顺序存储。各节点独立分布在内存(堆)中。

  • 比喻:火车车厢。每节车厢独立,通过挂钩(指针)连接,可灵活增删车厢。

2. 节点(Node)结构

单链表每个节点包含两个域:

  • 数据域:存储实际数据(如int data

  • 指针域:存储下一个节点的地址,称为“后继指针”或next

  • // 数据类型重定义,便于后续修改存储的数据类型
    typedef int SLTDataType;

    // 单链表节点结构体
    typedef struct SListNode {
    SLTDataType data; // 存储的数据
    struct SListNode* next; // 指向下一个节点的指针
    } SLTNode;

  • next指针的类型必须是struct SListNode*。因为它指向的是下一个同类型的节点。这是链表定义的核心,不可以使用SLTNode* next; ------->编译报错!C语言编译器是向上编译的,不能在结构体内部使用结构体别名。此时 SLTNode 还未被定义

定义加打印函数测试:

3. 头指针与空链表

  • 头指针:一个指向第一个节点的指针(如struct SListNode* plist)。

  • 空链表:头指针plist == NULL,表示链表不存在任何节点。

  • 按需申请释放节点,无空间浪费

  • 插入删除节点只需修改指针指向,头部操作时间复杂度为O(1)

  • 不支持随机访问,查找元素需从头遍历。

特性

顺序表

单链表

初始化

需要初始化容量(size)和大小(capacity)

无需初始化

空状态

数组指针为NULL,或size=0

头指针phead = NULL即可

原因

底层是连续数组,需要管理内存空间

节点离散分布,只需管理头指针

4.二级指针传参深度解析

核心原理:形参的改变不影响实参,若要修改实参的值,必须传递实参的地址。

1. 误区纠正
  • 错误认知:看到形参是一级指针就认为是“传地址”。

  • 正确认知

    • 无论是指针变量还是普通变量,都是内存中的变量,都有属于自己的地址。

    • 传地址的标志:必须使用取地址符&(数组名除外,因为数组名本身代表首元素地址)。

    • 若传plist(一级指针),传的是该指针变量存储的值(即某个节点的地址);若要修改plist本身,使其指向新节点,必须传&plist(一级指针的地址)。

2. 类比理解

变量

类型

存储的值

自身地址

a

int

1

0x100

pa

int*

0x100

0x800

  • 修改a的值,需传&a(0x100) 给int*接收。

  • 修改pa的值,需传&pa(0x800) 给int**接收。

二.单链表的操作

(1)尾插

算法思路:

  1. 申请新节点。

  2. 若链表为空(phead == NULL),直接将新节点作为头节点。

  3. 若链表非空,遍历找到尾节点(尾节点特征:next == NULL),将尾节点的next指向新节点。

易错点(重点)

  1. 参数必须传二级指针:因为可能需要修改头指针本身(如空链表插入时)。如果传一级指针SLTNode* phead,函数内修改的是形参,实参不会改变。

  2. 若链表为空,插入第一个节点时需要改变头指针plist的指向。结论凡是可能改变头指针指向的函数(如头插、尾插、头删、尾删),形参必须设计为**二级指针SLTNode**

  3. 找尾循环条件:必须是ptail->next != NULL,而非ptail != NULL。如果使用后者,循环结束时ptail为NULL,无法连接新节点。

(2)头插

算法思路:

  1. 创建新节点newNode

  2. newNodenext指向原头节点*pphead

  3. 将头指针*pphead指向newNode

(3)尾删

算法思路

  1. 断言检查:链表不能为空(*pphead != NULL)。

  2. 特殊情况:若只有一个节点((*pphead)->next == NULL),直接释放头节点并置空。

  3. 一般情况

    • 定义两个指针,ptail指向当前节点,prev指向ptail的前一个节点。

    • 遍历链表,找到尾节点ptail

    • prevnext置为NULL

    • 释放ptail

(4)头删

算法思路

  1. 断言检查:链表不能为空。

  2. 保存原头节点的下一个节点地址(next)。

  3. 释放原头节点。

  4. 将头指针*pphead指向保存的next节点

(5)查找

算法思路

  1. 遍历链表,比较当前节点数据与目标值x

  2. 找到则返回该节点指针,否则返回NULL

(6)在指定位置前插入

算法思路:

  1. 断言ppheadpos均不能为空。

  2. 特殊情况:若pos恰好是头节点(即*pphead == pos),直接调用头插函数。

  3. 一般情况

    • 定义prev指针,遍历找到pos的前一个节点。

    • 创建新节点newnode

    • 建立链接:prev->next = newnode; newnode->next = pos;

(7)在指定位置之后插入节点

算法思路:

已知pos节点,要插入数据为x的新节点newNode

  1. 通过SLTBuyNode(x)创建新节点
  2. newNode->next指向pos->next(即原后继节点)
  3. pos->next指向newNode

易错点:

// 错误:先改 pos->next,会导致原后继节点丢失
pos->next = newNode;
newNode->next = pos->next; // 此时 pos->next 已经是 newNode,相当于自指!

重点:如果先执行pos->next = newNode,那么pos->next的指向已经发生改变了,此时再通过pos->next去找原来的后继节点(如节点"4")就找不到了,导致链表断裂。newNode->next会指向newNode自身,原后继节点无法访问。

正确:

先连后面,再连前面
newNode->next = pos->next; // 第一步:newNode 指向原 pos 的后继
pos->next = newNode; // 第二步:pos 指向 newNode

(8)删除指定位置的节点

算法思路:

已知pos节点,要删除它:

  1. 如果pos是头节点 → 执行头删
  2. 否则 → 从头遍历找pos的前驱prev
  3. prev->next指向pos->next(跨过pos
  4. free(pos)并将pos置为NULL

易错点1:删除头节点需要特殊处理

如果pos恰好是头节点,从头开始找前驱的代码会失效——prev永远找不到prev->next == pos,会导致死循环或对空指针解引用崩溃。必须单独判断pos == *pphead走头删逻辑。

易错点2:必须先改指针再释放

如果先free(pos),则pos变成野指针,无法再通过pos->next获取后继节点地址,链表断裂。所以必须先让前驱跨接,再释放pos

(9)删除指定位置之后的节点

算法思路

已知pos节点,要删除pos->next

  1. del指针保存pos->next(即待删节点)
  2. pos->next指向del->next(跨过待删节点)
  3. free(del)并将del置为NULL
  4. 相较删除指定位置节点,删除其后继无需找前驱,效率更优——这是链表相比顺序表的重要优势。

易错点:pos->next必须非空

删除pos之后的节点,不仅要求pos不能为空,pos->next也不能为空。如果pos是尾节点(pos->next == NULL),没有后继可删,必须断言assert(pos->next)拦截,否则del为空,free(NULL)虽不崩溃但逻辑错误。

(10)销毁链表

算法思路:

逐个释放所有节点,最后将头指针置NULL

  1. 定义pcur指向头节点,next保存下一节点
  2. 循环:保存pcur->nextfree(pcur)pcur移到下一节点
  3. 循环结束,将*pphead置为NULL

易错点1:必须用二级指针

销毁链表需要将头指针本身置为NULL,所以要传头指针的地址(二级指针SLTNode** pphead),否则修改不会影响实参。

易错点2:释放前必须先保存后继

如果先free(pcur)再访问pcur->nextpcur已是野指针,无法找到下一个节点。所以必须在释放前用next保存pcur->next

关于free后是否置NULL的讨论

  • 函数内的局部变量(如pcurnext)跳出作用域自动销毁,不置NULL不影响程序正确性
  • 养成良好习惯free后将指针置NULL,可避免后续误用时对野指针解引用
  • 对于传递给函数的外部指针(如pos),free后置NULL是必要习惯

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/28 7:53:57

模型不听话

提示词不够强硬: 你可能在系统提示词里写了“你是一个天气助手”,但没有明确告诉它:“当用户询问天气时,你必须调用 get_weather 工具,而不是直接回答。”模型理解偏差: 对于简单的天气问题(如“…

作者头像 李华
网站建设 2026/8/28 7:52:30

MATLAB正态拟合直方图:从数据可视化到统计特征提取

1. 项目概述:从数据直方图到统计洞察 当你拿到一组实验数据、用户行为记录或者任何观测值时,第一反应是什么?对于很多理工科背景的朋友,尤其是学生和科研工作者,用MATLAB画个直方图(Histogram)看…

作者头像 李华
网站建设 2026/8/28 7:50:19

配送中心选址数学建模:从P-中值模型到混合整数规划实战

1. 项目概述:从实际问题到数学模型的跨越 配送中心选址,这听起来像是一个纯粹的物流管理问题,但当你真正深入进去,会发现它本质上是一个披着商业外衣的数学优化难题。无论是电商巨头规划其全国性的仓储网络,还是连锁超…

作者头像 李华
网站建设 2026/8/28 7:39:49

实验 15:Ansible Vault 加密敏感数据

文章目录 实验 15:Ansible Vault 加密敏感数据 一、实验概述 二、学习目标 三、前置知识与环境准备 3.1 前置知识 3.2 环境准备 四、核心概念深度解析 4.1 Ansible Vault 加密原理 4.2 四种密钥提供方式对比 五、实验步骤详解 步骤 1:创建 Vault 密码文件 步骤 2:创建明文变…

作者头像 李华