深入解析:链表的核心思想

news/2025/10/23 20:14:14/文章来源:https://www.cnblogs.com/slgkaifa/p/19161560

深入解析:链表的核心思想

链表的核心思想

1. 数组转化为单链表

class ListNode {
int val;
ListNode next;
ListNode(int x) {val = x};
}
// 将数组转化为一个单链表
ListNode createLinkedList(int[] arr) {
if (arr == null || arr.length == 0) {
return null;
}
ListNode head = new ListNode(arr[0]);
ListNode cur = head;
for(int i = 1; i < arr.length; i++) {
cur.next = new ListNode(arr[i]);
cur = cur.next;
}
return head;
}

代码疑难点

cur.next = new ListNode(arr[i]); // 把新建的节点接到当前节点后面
cur = cur.next; // 当前指针移动到下一个节点去	

2. 单链表的头插

// 创建一个单链表
ListNode head = createLinkedList(new int[]{1, 2, 3, 4, 5});
// 在单链表前面进行头插
ListNode cur = new ListNode(int[0]); // 创建一个新节点
cur.next = head;
head = cur;

代码疑难点

1 -> 2 -> 3 -> 4 -> 5
cur.next = head; // 将新节点指向了1这个节点,这个节点也就是 head
// 变成了 0 -> 1 -> 2 -> 3 -> 4 -> 5 // 此时,head 指针还停留在 1 这个节点上
head = cur; // 将 head 指针走向了 0 这个位置

3. 单链表的尾插

// 创建一条单链表
ListNode head = createLinkedList(new int[]{1, 2, 3, 4, 5});
// 在链表的尾部插入一个数字6
ListNode p = head; // 一般另起一个指针来进行遍历,不使用原来的头结点
// 首先,得将指针移动到链表尾部
while (p.next != null) {
p = p.next;
}
// 1 -> 2 -> 3 -> 4 -> 5 // 现在 p 指针指向了 5 数字
p.next = new ListNode(6);
// 这样就完成了:1 -> 2 -> 3 -> 4 -> 5 -> 6

代码疑难点

// 为什么 while(p != null || p.next != null) 不是这个?而是只需要p.next != null 即可?
// 原因就是:如果 head != null ,p 一定不会等于 null,最终会停在尾节点。

4. 在单链表中间插入新元素

// 创建一条单链表
ListNode head = createLinkedList(new int[]{1, 2, 3, 4, 5});
// 在坐标3中插入一个数字66
ListNode p = head;
for (int i = 0; i < 2; i++) {
p = p.next;
}
// 创建一个新节点
ListNode newNode = new ListNode(66);
newNode.next = p.next;
// 插入新节点
p.next = newNode;
// 最终变成了 1 -> 2 -> 3 -> 66 -> 4 -> 5

代码疑点

// 创建一个新节点
ListNode newNode = new ListNode(66);
newNode.next = p.next; // p.next 指向了4这个数字,66 -> 4
// 插入新节点
p.next = newNode; // p 的下一个节点指向了 newNode 变成了:3 -> 66 
// 最终变成了 1 -> 2 -> 3 -> 66 -> 4 -> 5

5. 在单链表中删除元素

// 创建一个单链表
ListNode head = new ListNode(new int[]{1, 2, 3, 4, 5};
// 删除第 4 个节点,要操作前驱节点
ListNode p = head;
// 走到要删除节点的前驱节点,也就是3这个节点
for (int i = 0; i < 2; i++) {
p = p.next; // 往下走
}
// 删除也就是不连接 4 这个节点得了
p.next = p.next.next; // 3 的下一个下一个节点 -> 是 5
// 现在链表:1 -> 2 -> 3 -> 5

注意点:【删除】就是:将要删除的节点的前驱节点 -> 连接其后驱节点

6. 在单链表尾部删除元素

// 创建一个单链表
ListNode head = createLinkedList(new int[]{1, 2, 3, 4, 5});
// 在尾部删除元素:找到倒数第二个元素
ListNode p = head;
while(p.next.next != null) {
p = p.next; // 往下走
}
// 走到这个状态:p 指向了 4
// 不指向 5 了,以 4 为结尾好了
p.next = null;
// 现在链表变成了 1 -> 2 -> 3 -> 4

7. 在单链表头部删除元素

// 这个更简单,头节点往下走一步好了,这样就把头节点删掉了
// 创建一个单链表
ListNode head = createLinkedList(new int[]{1, 2, 3, 4, 5});
// 头节点往下走一步
head = head.next;
// 现在变成:2 -> 3 -> 4 -> 5

不过,读者会不会有一个疑问:前面的 1 应该还会指向 2 ,会不会造成内存泄漏?

答案是不会。旧的头节点指向别的节点没有关系,只要没有其他节点指向这个旧的头结点即可,这样,旧的头节点就会被垃圾回收收掉。

8. 双链表的基本操作

// 创建一条双链表
// 创建节点
class DoublyListNode {
int val;
DoublyListNode prev, next;
DoublyListNode(int x) {val = x};
}
DoublyListNode createDoublyLinkedList(int arr) {
// 判断
if (arr == null || arr.length == 0) {
return null;
}
// new 一个头节点
DoublyListNode head = new DoublyListNode(arr[0]);
DoublyListNode cur = head;
// 开始创建链表
for (int i = 1; i < arr.length; i++) {
// 双向链表,最重要的是双向
DoublyListNode newNode = new DoublyListNode(arr[i]);
cur.next = newNode; // cur -> newNode
newNode.prev = cur; // cur <- newNode
cur = cur.next; // cur 指针往下走
}
return head;
}

9. 双链表的遍历/查找/修改

// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new arr[]{1, 2, 3, 4, 5});
DoublyListNode tail = null;
// 从头节点向后遍历链表
for (DoublyListNode cur = head; cur != null; cur = cur.next) {
System.out.println(cur.val);
tail = cur; // 尾节点跟随 cur 指针的步伐
}
// 从尾节点向前遍历链表
for (DoublyListNode cur = tail; cur  != null; cur = cur.prev) {
System.out.println(cur.val);
}

10. 在双链表头部插入元素

// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new arr[]{1, 2, 3, 4, 5});
// 先 new 一个节点 0
DoublyListNode newNode = new DoublyListNode(0);
newNode.next = head; // 0 -> 1
head.prev = newNode; // 0 <- 1
head = newNode; // head 往 newNode 走,因为 head 得变成头节点,原本 head 在 1 的位置,不符合规则,所以跑回 0 这个位置
// 结果变成:0 -> 1 -> 2 -> 3 -> 4 -> 5

11. 在双链表尾部插入一个新元素

// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});
DoublyListNode tail = head;
// 先走到链表的尾部
while (tail.next != null) {
tail = tail.next;
}
// 现在 tail 指针指向了 5 这个位置
// new 一个新的节点 6
DoublyListNode newNode = new DoublyListNode(6);
tail.next = newNode; // tail -> newNode
newNode.prev = tail; // tail <- newNode
// 现在 tail 还是指向 5
tail = newNode; // tail 顾名思义,是尾节点的意思,所以得指向 6,所以,就往 newNode 这挪,最终 tail 就指向 6
// 现在链表变成了 1 -> 2 -> 3 -> 4 -> 5 -> 6

12. 在双链表中间插入新元素

在双链表指定位置插入节点,需要调整该位置的前驱节点和后续节点的指针。

比如下面的例子,把元素 66 插入到索引 3(第 4 个节点)的位置:

// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});
DoublyListNode cur = head;
// 想要在索引 3(第 4 个节点)插入元素
// cur 得走到 索引 3 的前一个位置:索引 2
for (int i = 0; i < 2; i++) {
cur = cur.next;
}
// new 一个新节点 66
DoublyListNode newNode = new DoublyListNode(66);
// 得呈现一个这个状态:cur -> newNode -> cur.next
newNode.next = cur.next; // newNode -> cur.next
cur.next = newNode; // cur -> newNode
// 双链表,讲究一个双向
cur.next.prev = newNode; // newNode <- cur.next
newNode,prev = cur; // cur <- newNode
// 现在链表变成了 1 -> 2 -> 3 -> 66 -> 4 -> 5

13. 在双链表中删除一个节点

在双链表中删除元素,得调整要删除元素的前驱节点以及其后续节点的指针:

// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});
// 删除第 4 个节点
// 先找到第 3 个节点
DoublyListNode cur = head;
for (int i = 0; i < 2; i++) {
cur = cur.next;
}
// 现在 cur 指针指向了 3
DoublyListNode toDelete = cur.next; // toDelete 节点指向 4
// 只需要将 toDelete 的前驱节点 -> 后续节点,跳过 toDelete 就好了
cur.next = toDelete.next; // cur -> toDelete.next
toDelete.next.prev = cur; // cur <- toDelete.next
// 把 toDelete 的前后指针都去置为 null (可选,一个好习惯)
toDelete.next = null;
toDelete.prev = null;
// 现在链表变成了 1 -> 2 -> 3 -> 5

14. 在双链表头部删除元素

删除双链表需要调整头节点的指向

// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});
// 删除头节点,让 head 往下走一个步即可
DoublyListNode toDelete = head;
head = head.next;
// 现在 head 已经指向了 2
head.prev = null; // 将 2 的前驱节点置为空
//清除已删除节点的指针
toDelete.next = null;
// 现在链表变成了 2 -> 3 -> 4 -> 5

15. 在双链表尾部删除元素

在单链表中,由于缺乏前驱节点,所以只能遍历到尾节点的前一个节点,再进行操作,也就是:cur.next.next != null 操作它的 next 指针才能将尾节点删掉。但在双链表中,由于每个节点都存储其自身的前驱节点的指针,所以,在双链表中,可以直接操作尾节点,也就是:cur.next != null ,把自己从链表中摘掉。

// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});
// 删除尾节点 5
DoublyListNode cur = head;
while (cur.next != null) {
cur = cur.next;
}
// 现在 cur 走到了 5 这个位置
cur.prev.next = null; // cur.prev 指向的是 4  4 的 next 就是 5 所以把 5 给断掉了
cur.prev = null; // 把被删结点的指针都断开是个好习惯(可选)
// 现在链表变成了 1 -> 2 -> 3 -> 4

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

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

相关文章

AI元人文构想:参与“自由与责任”哲学思考——岐金兰之实验

AI元人文构想:参与“自由与责任”哲学思考——岐金兰之实验 让我们以“AI元人文”构想为透镜,重新审视“自由与责任”这一古老的哲学谜题。这将不再是一场纯粹的思辨,而是一次为智能行为构建底层架构的思想实验。 基…

20251023

总结 A 预计:100,实际:60 用时:10min思路历程:居然看错题了,以为可以整个一段全部是一个字母,关键是样例刚好全部能过 正解:666 收获:最好多看几遍题和数据范围B 预计:100,实际:100 用时:30min思路历程:…

Java常用机制 - SPI机制详解

目录Java常用机制 - SPI机制详解简单介绍SPI工作流程SPI实现代码示例步骤 1:定义服务接口步骤 2:提供具体实现(由不同厂商提供)步骤 3:创建配置文件步骤 4:使用 ServiceLoader 发现并调用服务输出可能为:需要SP…

实用指南:用户研究:用户研究和数据分析的根本联系与区别

实用指南:用户研究:用户研究和数据分析的根本联系与区别2025-10-23 20:01 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important…

2025.10.23——2绿2蓝

普及+/提高 P2914 [USACO08OCT] Power Failure G 最短路,简单预处理 P9912 [COCI 2023/2024 #2] Zatopljenje T1,离线树状数组 提高+/省选- P9906 [COCI 2023/2024 #1] Kocke T2,没想到的DP P9031 [COCI 2022/2023 …

Anaconda命令大全conda

创建虚拟环境 conda create --name myenvpython37 python=3.7使用该虚拟环境 conda activate myenvpython37退出使用虚拟环境 conda deactivate

完整教程:状态管理库 Zustand 的接入流程与注意点

完整教程:状态管理库 Zustand 的接入流程与注意点pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas"…

采用opencv来识别信用卡的号码

采用opencv来识别信用卡的号码pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco"…

塔吊施工环境与附属设施监测!思通数科 AI 卫士筑牢全场景安全防线

塔吊施工安全不仅依赖核心部件与人员操作合规,周边环境与附属设施的隐患也常成为 “安全盲区”:塔吊周边地面是否存在积水、泥泞,易引发作业人员滑倒,但人工巡检易忽视此类环境细节;有限空间入口处安全标识、警示…

精读《C++20设计模式》:重新理解设计模式系列 - 详解

精读《C++20设计模式》:重新理解设计模式系列 - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&qu…

网络设备

1. 交换机(缓存接入的所有设备的ip,mac地址等) 2. 路由器(公司级,企业级)

Kafka-保证消息消费的顺序性及高可用机制 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

第二十二篇

今天是10月23号,上了算法课,体测1000米。

CSharp: Convert CSV to XLS Using Open XML SDK

using System; using System.Data; using System.IO; using System.Linq; using System.Text; using System.Globalization; using CsvHelper; using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Packaging…

实用指南:PyTorch 数据处理工具箱:从数据加载到可视化的完整指南

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

《程序员修炼之道:从小工到专家》阅读笔记1

2025年的今天,当AI代码助手能自动生成70%基础代码时,程序员的核心竞争力究竟是什么?《程序员修炼之道》序言以"生存指南"的定位给出震撼答案:真正的高手从不依赖工具,而是凭借责任意识、持续学习与批判…

多级多卡训练模型时有些参数没有参与loss计算和梯度更新的解决办法

在运行程序的bash命令中添加 export TORCH_DISTRIBUTED_DEBUG=DETAIL ,这样就可以在log或终端打印没有参与loss计算的权重参数了。

负载均衡及三种软件负载

[!TIP] 环境用nginx反向代理文档里的三台服务器即可一、基于nginx的负载均衡 七层负载:配置nginx.conf主配置文件 vim /etc/nginx/nginx.conf 在http块内添加: upstream userLB(随意){ #server写的ip:端口号(8080为t…

在 GEO / AIO 角度:如何优化 SEO 内容?

一、先把优化方向说清楚 现在做内容的目标不仅仅是 SEO 排第几,而是被选进 AI 答案。 AI 搜索会把页面切成可复用的小块,再按权威与相关性拼答案。传统 SEO 的可抓取、元数据、内链、外链仍是地基,但想被选中,核心…

Android Handler的runWithScissors手段

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …