当前位置: 首页 > news >正文

带头结点的单链表删除指定位置结点


1. 功能概述

bool ListDelete(LinkList &L, int i, ElemType &e) 函数的功能是:在带头结点的单链表 L 中,删除第 i 个位置的结点,并将被删除结点的数据通过引用参数 e 带回给调用者。

  • 函数名: ListDelete,清晰表达了其功能。
  • 返回值: bool 类型。true 表示删除成功,false 表示删除失败(例如,位置 i 不合法)。
  • 参数:
    • LinkList &L: 链表的头指针(指向头结点),使用引用传递,意味着函数内部对链表的修改会直接作用于原始链表。
    • int i: 要删除的结点的位序(从1开始计数)。
    • ElemType &e: 一个引用参数,用于“返回”被删除结点的数据值。这是一种常见的C++技巧,比使用全局变量或指针返回值更安全、更优雅。

2. 代码逐行分析

bool ListDelete(LinkList &L, int i, ElemType &e) {// 1. 边界条件检查:位序是否合法if (i < 1) return false;          
  • 逻辑: 链表的位序是从1开始的。如果传入的 i 小于1,它肯定是一个非法位置。
  • 优点: 这是最先进行的检查,可以快速处理无效输入,提高代码的健壮性。这是一种“防御性编程”的体现。
    // 2. 初始化指针和计数器LNode *p = L;                     // p指向头结点int j = 0;                        // 计数器
  • 逻辑:
    • LNode *p = L;: 声明一个指针 p,并让它指向链表的头结点。这是遍历链表的起点。注意p 指向的是头结点,而不是第一个数据结点。头结点的位序可以看作是 0
    • int j = 0;: 声明一个计数器 j,用来记录当前指针 p 指向的是第几个结点。因为 p 从头结点(位序0)开始,所以 j 初始化为 0
    // 3. 循环查找第 i-1 个结点while (p != NULL && j < i-1) {    p = p->next;j++;}
  • 逻辑: 这是整个算法的核心步骤之一。循环的目标是让指针 p 移动到待删除结点的前驱结点上。
    • 为什么是 i-1 因为在单链表中,要删除一个结点,必须找到它的前一个结点,然后通过修改前驱结点的 next 指针来“跳过”待删除结点。我们无法直接访问一个结点的前驱。
    • 循环条件:
      • p != NULL: 确保指针没有走到链表的末尾。如果 p 已经是 NULL,说明链表长度不足 i-1,位置 i 肯定不合法。
      • j < i-1: 只要计数器 j 还没达到 i-1,就继续向后移动。
  • 循环结束后 p 的状态:
    • 成功情况: p 指向了第 i-1 个结点。
    • 失败情况: p 变成了 NULL,说明链表长度小于 i-1
    // 4. 再次检查位置i的合法性if (p == NULL || p->next == NULL) return false;
  • 逻辑: 在 while 循环结束后,需要进行一次最终的合法性检查。这个检查非常关键,它处理了两种失败情况:
    • p == NULL: 对应 while 循环因为 p != NULL 条件不满足而退出的情况。说明链表太短,根本没有第 i-1 个结点。
    • p->next == NULL: 这种情况更微妙。它说明虽然找到了第 i-1 个结点(p不为空),但这个结点是链表的最后一个结点,它后面没有第 i 个结点了。因此,i 的值等于链表长度+1,同样是一个非法位置。
  • 优点: 这个双重检查确保了所有非法的 i 值(太小或太大)都能被捕获。
    // 5. 执行删除操作LNode *q = p->next;               // q指向待删除结点e = q->data;                      // 保存删除结点的值p->next = q->next;                // 前驱结点跳过qfree(q);                          // 释放q的内存
  • 逻辑: 如果通过了所有检查,说明删除操作是安全的,可以执行。
    • LNode *q = p->next;: 创建一个临时指针 q,让它指向待删除的结点(即第 i 个结点)。这样做是为了后续能安全地释放它的内存。
    • e = q->data;: 将待删除结点的数据存入引用参数 e,以便调用者获取。
    • p->next = q->next;: 这是删除操作的核心。将前驱结点 pnext 指针,修改为指向待删除结点 q 的后继结点。这样,从 p 开始遍历,就会跳过 qq 结点就从链表中“断开”了。
    • free(q);: 至关重要的一步。使用 free() 函数释放 q 所指向的内存空间。如果不释放,就会造成内存泄漏(Memory Leak),被删除的结点所占用的内存将永远无法被程序再次使用,直到程序结束。
    // 6. 返回成功return true;
}
  • 逻辑: 所有操作都成功完成,返回 true 表示删除成功。

3. 算法复杂度分析

  • 时间复杂度: O(n)
    • 最坏情况:删除链表的最后一个结点(i = n),需要从头结点开始遍历整个链表,执行 n-1 次移动。
    • 最好情况:删除第一个结点(i = 1),while 循环条件 j < 0 不满足,直接跳过,时间复杂度为 O(1)。
    • 平均情况:在任意位置删除的概率均等,平均需要移动 (n-1)/2 次。
    • 综合来看,时间复杂度与链表长度 n 呈线性关系,因此为 O(n)。主要时间消耗在查找第 i-1 个结点的过程。
  • 空间复杂度: O(1)
    • 算法只使用了固定数量的指针变量(p, q)和整型变量(j),这些额外占用的内存空间不随链表规模 n 的变化而变化。因此,空间复杂度是常数级 O(1)

4. 优点与潜在问题

优点:

  1. 逻辑清晰,结构标准: 完全遵循了“检查 -> 查找 -> 修改 -> 释放”的标准单链表操作范式,易于理解和教学。
  2. 健壮性强: 通过 if (i < 1)if (p == NULL || p->next == NULL) 两道防线,处理了所有可能的非法输入,保证了程序的稳定性。
  3. 正确性高: 代码逻辑严谨,特别是对 p->next == NULL 的检查,确保了不会对链表末尾之后的位置进行操作。
  4. 资源管理良好: 使用 free(q) 释放了被删除结点的内存,避免了内存泄漏,这是良好编程习惯的体现。
  5. 接口设计合理: 使用引用参数 &e 返回被删除的元素值,比使用指针或全局变量更符合现代C++的编程风格。

总结

这是一段高质量、教科书级别的单链表删除操作实现。它清晰地展示了算法的核心思想,并且在健壮性和资源管理方面都做得很好。对于学习数据结构的初学者来说,这是一个极佳的范例。唯一的改进点是在纯C++项目中,应将 free() 替换为 delete 以保持内存管理风格的一致性。

http://www.wxhsa.cn/company.asp?id=3753

相关文章:

  • 《文字、语言与数字的奇妙联结》读后感,大公司内部编码规范,本学期编码遵守规范
  • [HTTP/Spring] RestTemplate : Spring的HTTP网络请求框架
  • 深入解析:Linux使用-MySQL的使用
  • 博客园-我的博客-的皮肤更换
  • Apache Commons Math3 使用指南:强大的Java数学库 - 教程
  • HarmonyOS图形处理:Canvas绘制与动画开发实战
  • script setup 在 Vue 3 中的核心作用及具体实现方式
  • 0voice-1.4.1-cmake
  • test test test
  • 容器化改造基本原理
  • Blogroll 友链
  • Java 字节码与 ASM 框架实战解析
  • 计算机大数据毕业设计选题:基于Spark+hadoop的全球香水市场趋势分析系统 - 详解
  • Dos的常用命令
  • 持续集成自动化CI/CD
  • Lightroom Classic 2025(LRC 2025)安装教程(附直接安装包下载)+入门操作指南
  • 2025/09/14 【二叉树11】完全二叉树的节点个数
  • 8888
  • 接口限流代码 - 实践
  • OutGuess 安装与问题排查指南(Kali Linux 环境)
  • 拓展操作码举例
  • TryHackMe | Cicada-3301 Vol:1
  • 完整教程:Word添加图/表题注
  • [MCP][01]简介与概念
  • CF819B Mister B and PR Shifts
  • 第一次自我介绍
  • 在Linux环境部署Flask应用并启用SSL/TLS安全协议
  • 0127_责任链模式(Chain of Responsibility)
  • 洛枫娜娜米讨厌数学……?
  • Spatial 语言核心概念简介