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

2.3 单链表的应用

DATA: 2025-07-12 09:03
TAGS: [[数据结构]]
Course: #比特数据结构
Author: ApolloMonasa
Repository: CSNotes | Code Is Here
Pre: 2.1 顺序表
Next: ???

2.3 单链表的应用

算法题

在开始做题之前,还是规矩,笔者简单说几点注意吧!

  • 解引用之前切记考虑这个指针是否可能为空
  • 加强概念的印象,比如哪些地方建议把指针置空,比如链表的结尾。
  • 熟练掌握链表的增删改查

移除链表元素

虚拟头+遍历删除

在这里插入图片描述

这个技巧我们在上一节中说过,在此不会过多赘述。

递归

在这里插入图片描述

这个问题具有递归性质,我们直接看代码就能看懂,调用自身处理从第二个节点往后的链表,再讨论要不要去除头节点。

反转链表

头插

在这里插入图片描述

遍历链表,每遍历到一个都头插到新链表上,很简单。

直接反向

![[Pasted image 20250712194003.png]]

用三个指针,p3在前面记住p2的下一个位置,p1和p2用来反向,不难想到最后一次反向p2指向最后一个元素,那么下一次p2就指向NULL,所有终止条件是p2为NULL。

递归

在这里插入图片描述

链表的中间结点

快慢指针

在这里插入图片描述

数组法

遍历链表,存入数组,最后可以轻易取得中间值。
在这里插入图片描述

先遍历计数

用单个指针,先遍历一遍得到链表长度,再遍历一遍取得中间值。
在这里插入图片描述

合并两个有序链表

归并排序

在这里插入图片描述

递归

在这里插入图片描述

这两种方法其实本质上相同。

链表的回文结构

递归

这是比较容易想到的一个方法,每次比较链表的头和尾,然后剩下的部分调用自身进行判断。
就是时间复杂度有点高,是O(n2)O(n^2)O(n2),能过,也推荐,如果数据加强,就要用其他方法了。
在这里插入图片描述

反转后与自身比较

其实无论判断回文结构一个重要且主要的方法就是反转后对比,如果相同就是回文结构
这个方法笔者尝试实现过,但是发生了段错误,我猜测是OJ不允许我开这么多的内存。

反转后半段再比较

在这里插入图片描述

链表分割

创建新链表

在这里插入图片描述

创建前后两个链表,一个放小于x的节点,其他的放另一个,最后把这两部分连接起来就行。

随机链表的复制

这题要求我们对这样的链表结构进行深拷贝,不能使用原有节点,需要自己开辟。
在这里插入图片描述

回溯

可以用一个哈希表来记录"旧->新"的映射,并用递归函数的设计方式处理,如果为空就返回空指针,如果已经处理过就返回记录好的新节点,否则,我们就要再开辟一个节点进行深拷贝,同时也要记录这个新的映射,编码简单,建议掌握。
在这里插入图片描述

复制分裂法

在这里插入图片描述

这个算法的核心思想是:

  1. 复制并交织:在每个原始节点后面创建一个新的复制节点,将两个链表“编织”在一起。
  2. 链接随机指针:利用交织的结构,轻松地为新节点设置 random 指针。
  3. 拆分链表:将交织的链表拆开,恢复原始链表,并得到最终的深拷贝链表。

假设我们的原始链表如下:

  • 有两个节点,Node1 (值为 7) 和 Node2 (值为 13)。
  • Node1 的 next 指向 Node2。
  • Node2 的 next 指向 nullptr
  • Node1 的 random 指向 nullptr
  • Node2 的 random 指向 Node1。
初始状态

这是我们开始操作前的链表结构。

原始链表
random
null
Node2 (val: 13)
Node1 (val: 7)

第一步:复制节点并交错链接

对应代码:

for (Node* node = head; node != nullptr; node = node->next->next) {Node* nodeNew = new Node(node->val);nodeNew->next = node->next;node->next = nodeNew;
}

说明
我们遍历原始链表。对于每个节点 node,我们创建一个新的节点 nodeNew,并将其插入到 nodenode->next 之间。

  • 遍历到 Node1,创建 Node1',将它插入到 Node1Node2 之间。
  • 遍历到 Node2,创建 Node2',将它插入到 Node2null 之间。

操作后的状态
链表变成了 “原1 -> 新1 -> 原2 -> 新2 -> null” 的交织结构。此时,新节点的 random 指针还是空的。

第一步后:交织链表
random
Node1' (val: 7)
Node1 (val: 7)
Node2 (val: 13)
Node2' (val: 13)
null

第二步:复制随机指针

对应代码:

for (Node* node = head; node != nullptr; node = node->next->next) {Node* nodeNew = node->next;nodeNew->random = (node->random != nullptr) ? node->random->next : nullptr;
}

说明
再次遍历这个交织的链表,这次我们专门处理 random 指针。
对于每个原始节点 node,它的复制节点 nodeNew 就在它的 next 位置。
关键在于: nodeNewrandom 指针,应该指向 noderandom 指针所指向节点的 复制节点

由于我们的交织结构,node->random 指向的任何原始节点 X,它的复制节点 X' 恰好就是 X->next
所以,我们可以通过 node->random->next 来找到正确的 random 目标。

  • 对于 Node1: node->randomnullptr,所以 Node1'random 也设为 nullptr
  • 对于 Node2: node->random 指向 Node1。那么 Node2'random 就应该指向 Node1 的下一个节点,也就是 Node1'。即 N2_copy->random = N2->random->next

操作后的状态
新节点的 random 指针被正确设置。

第二步后:设置新节点的Random指针
original random
new random
Node1' (val: 7)
Node1 (val: 7)
Node2 (val: 13)
Node2' (val: 13)
null

第三步:拆分两个链表

对应代码:

Node* headNew = head->next;
for (Node* node = head; node != nullptr; node = node->next) {Node* nodeNew = node->next;node->next = node->next->next;nodeNew->next = (nodeNew->next != nullptr) ? nodeNew->next->next : nullptr;
}
return headNew;

说明
最后一步,我们将这个交织的链表“解开”,分成两个独立的链表。
我们遍历链表,同时恢复原始链表的 next 指针和设置新链表的 next 指针。

  • 处理 Node1 和 Node1’:
    • Node1->nextNode1' 改为 Node1'->next (即 Node2)。
    • Node1'->nextNode2 改为 Node2->next (即 Node2')。
  • 处理 Node2 和 Node2’:
    • Node2->nextNode2' 改为 Node2'->next (即 null)。
    • Node2'->nextnull 改为 null

经过这一步,我们就得到了两个完全解耦的链表:一个是恢复原状的原始链表,另一个是全新的深拷贝链表。

最终状态

最终得到的深拷贝链表 (函数返回值)
第三步后:恢复后的原始链表
random
random
null
Node2' (val: 13)
Node1' (val: 7)
null
Node2 (val: 13)
Node1 (val: 7)

通过这三步,我们就在不使用额外哈希表的情况下,以 O(N) 的时间复杂度和 O(1) 的额外空间复杂度完成了链表的深拷贝。

Reference


Recommend Links

http://www.dtcms.com/a/277091.html

相关文章:

  • 图像读取与模型保存--基于NWPU-RESISC45数据集的图像二分类实战
  • stm32f103c8t6移植freeRTOS内存不足报错问题的解决办法
  • 浏览器渲染原理与性能优化全解析
  • 快速傅里叶变换(FFT)中的振幅和相位
  • 【计算机网络架构】环型架构简介
  • 在 C# 中调用 Python 脚本:实现跨语言功能集成
  • ADB 调试日志全攻略:如何开启与关闭 `ADB_TRACE` 日志
  • CS课程项目设计1:交互友好的井字棋游戏
  • 详解Linux下多进程与多线程通信(二)
  • 【QT】使用QSS进行界面美化
  • 异或为什么叫异或
  • 【读书笔记】《Effective Modern C++》第3章 Moving to Modern C++
  • Datawhale AI夏令营——基于带货视频评论的用户洞察挑战赛
  • 【PTA数据结构 | C语言版】简单计算器
  • 17.使用DenseNet网络进行Fashion-Mnist分类
  • LabVIEW调用外部DLL
  • 深度学习图像分类数据集—七种树叶识别分类
  • 零基础 “入坑” Java--- 十、继承
  • ARC 03 从Github Action job 到 runner pod
  • PPO(近端策略优化)
  • 华为HarmonyOS 5.0深度解析:跨设备算力池技术白皮书(2025全场景智慧中枢)
  • 【C++】list及其模拟实现
  • C++--List
  • AI交互中的礼貌用语:“谢谢“的效用与代价分析
  • 【操作系统-Day 5】通往内核的唯一桥梁:系统调用 (System Call)
  • MVC 参考手册
  • C++值类别与移动语义
  • linux shell从入门到精通(一)——初识Shell程序
  • opencv中contours的使用
  • Spring Boot RESTful API 设计指南:查询接口规范与最佳实践