【数据结构】:用Java实现链表

在
ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比较低,因此ArrayList不适合做任意位置插入和删除比较多的场景。因此:java 集合中又引入了LinkedList,即链表结构。
概念
顺序表是物理上连续,逻辑上也是连续的
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。
链表是由一个一个的节点组织起来的,整体的组织就叫链表

注意:
- 从上图可看出,链式结构在逻辑上是连续的,但在物理上不一定连续
- 现实中的节点一般都是从堆上申请出来的
- 从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续
节点可以认为是节点对象,对象里面有两个节点属性,
val用来存储数据,next用来存储下一个节点的地址
分类
链表
- 单向/双向

- 带头/不带头: 带头就是带一个头节点,头结点的数据域可以不存储任何信息,也可以用来存储一些附加信息,如链表的长度等。

- 循环/不循环: 循环就是将最后一个节点里面地址改为第一个节点的地址

链表结构
- 单向带头循环
- 单向带头非循环
- 单向不带头循环
- 单向不带头非循环(重点)

- 双向带头循环
- 双向带头非循环
- 双向不带头循环
- 双向不带头非循环(重点)
链表的实现
链表的构造
节点的构造和连接
如何构造节点?
- 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部食物提供服务,那么这个内部类的完整结构最好使用
内部类 - 因为链表是由若干节点组成,而节点又是一个完整的结构,所以将节点定义为内部类。其最少有两个域,一个是数值域,一个是
next域,且next的类型为节点类型 - 最后对节点进行构造,只需要实例化这个内部类的对象即可,
实例化出来的对象便是节点
class ListNode { public int val; public ListNode next; public ListNode(int val) { this.val = val; }
}public ListNode head; public void createList() { ListNode node1 = new ListNode(12); ListNode node2 = new ListNode(23); ListNode node3 = new ListNode(34); ListNode node4 = new ListNode(45); ListNode node5 = new ListNode(56);
}
如何让它与第一个节点相关联?
- 通过访问
node1存储地址的变量next,将其的值赋为下一个节点的地址 - 以此类推
- 最后让头节点指向第一个节点
node1
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5; this.head = node1;
当
createList方法走完之后,实例化的各个节点对象就没有了,只保留了一个head对象
因为这些都是局部变量,方法调用完成之后,局部变量就被回收了
但不代表节点就没人引用了,他们被地址引用,谁引用了他们的地址,谁就引用他们
链表的功能
void display()——遍历链表
- 当
head == null的时候,链表就遍历完了。若写成head.next == null,则不会打印出最后一个节点的数据 - 要从第一个节点走到第二个节点,只需要
head == head. next即可。 - 但若想完成多次打印,
head的位置就不能变,需要一直在首位,所以我们就定义一个cur节点,来做head的遍历工作,head只负责站在最前面定位即可
node中的数据与其是否为null是两个独立的概念。在编程和数据结构中,node通常是一个对象或结构,它包含数据字段和一个或多个指向其他节点的指针或引用。
- 当我们说
node != null时,我们是在检查node这个变量是否指向了一个有效的内存地址,即它是否已经被初始化并且分配了内存。node中的数据字段可以包含任何类型的值,包括null(如果数据字段的类型允许)。但是,即使数据字段是null,node本身仍然可以是一个有效的对象,只是它的数据字段没有包含有用的信息。因此,
node != null并不表示node中的数据一定非空或有效。它只表示node这个变量已经指向了一个在内存中的对象
//遍历链表
public void display() { ListNode cur = head; while (cur != null) { System.out.print(cur.val + " "); cur = cur.next; } System.out.println();
}
int size()——求链表长度
- 定义一个
count变量,用来记录cur向后走的次数 - 每向后走一步,
count++
不能写成:
cur.next != null,因为最后一个节点的 next 为空,若是这样判断的话最后一个节点就不会进循环了,就会少算一个
//计算链表长度
public int size() { int count = 0; ListNode cur = head; while (cur != null) { count++; cur = cur.next; } return count;
}
void addFirst(int val)——头插法
- 将此时头结点的地址传给 node 的 next 变量
- 将头节点前移到 node 的地方
这里两步的顺序不可以交换,不然就是自己指向自己了
插入节点的时候,一般先绑后面,再插入前面
//头插
public void addFirst(int val) { ListNode node = new ListNode(val); node.next = head; head = node;
}
void addLast(int val)——尾插法
- 为了避免产生空指针异常报错,我们先对
head == null的情况进行讨论- 若头节点为空,则
head = node; - 记得加上
return,否则程序会继续执行下去
- 若头节点为空,则
- 若链表不为空,找到链表的尾巴
- 当
cur. next == null时,cur指向的就是尾巴
- 当
- 最后让
head.next == node;即可
//尾插
public void addLast(int val) { ListNode node = new ListNode(val); if (head == null) { head = node; return; } ListNode cur = head; while (cur.next != null) { cur = cur.next; } cur.next = node;
}
void addIndex(int index, int val)——在任意位置插入
- 判断
index的合法性- 定义一个
checkIndex(int index)方法用来检查index的合法性 - 若不合法,则抛出一个自定义异常:
IndexNotLeagalException
- 定义一个
index == 0 || index == size();- 前者相当于是头插,直接调用
addFirst() - 后者相当于是尾插,直接调用
addLast()
- 前者相当于是头插,直接调用
- 找到
index的前一个位置- 创建一个
findIndexSubOne(int index)方法 - 创建一个节点
cur来接收调用方法的返回值 - 最后
cur就是index位置的前一个节点了
- 创建一个
- 进行连接
- 实例化一个所带数据为
val的节点node node.next = cur.next;cur.next = node;
- 实例化一个所带数据为
//在任意位置插入
public void addIndex(int index, int val) { //1. 判断index的合法性 try { checkIndex(index); } catch (IndexNotLegalException e) { e.printStackTrace(); } //2. index == 0 || index == size() if(index == 0){ addFirst(val); return; } else if(index == this.size()){ addLast(val); return; } //3. 找到 index 的前一个位置 ListNode cur = findIndexSubOne(index); //4. 进行连接 ListNode node = new ListNode(val); node.next = cur.next; cur.next = node;
} //用来检查输入的 index 是否合法的方法
public void checkIndex(int index) { if(index < 0 || index > size()){ //若不合法则抛出一个异常throw new IndexNotLegalException("index位置不合法"); }
} //用来找到 index 前一位对应的节点的函数
private ListNode findIndexSubOne(int index) { ListNode cur = head; for (int i = 0; i < index - 1; i++) { cur = cur.next; } return cur;
}
boolean contains(int val)——链表中是否包含某个元素
- 遍历链表
- 若能在链表元素中找到
val值,则返回true - 否则返回
false
//判断链表中是否包含某个元素
public boolean contains(int val) { ListNode cur = head; while(cur != null){ if(cur.val == val){ return true; } } return false;
}
void remove(int key)——删除第一次出现的关键字的节点
- 首先判断是否为空链表
- 遍历链表
- 循环条件为:
cur.next != null
- 循环条件为:
- 找到 val 值的前一个节点 cur
- 当
cur.next.val == val时,找到目标
- 当
- 进行删除
- 找到之后,将其创建为
del节点 cur.next = del.next或者cur.next = cur.next.next- 看表达式可知,删除的判断是从第二个元素开始的,无法对第一个元素进行判断,所以需要针对第一个元素再加上一个删除判断:
head.val == val
- 找到之后,将其创建为
//删除第一次出现的关键字的节点
public void remove(int val) { //链表为空 if(head == null){ return; } //当第一个元素就为 val if(head.val == val){ head = head.next; return; } ListNode cur = head; while(cur.next != null){ if(cur.next.val == val){ ListNode del = cur.next; cur.next = del.next; } cur = cur.next; }
}
void removeAll(int val)——删除所有出现的关键字的节点
在原有的链表上进行修改
只遍历一遍链表
- 定义两个引用变量
- cur 代表当前需要删除的节点
- prev 代表当前需要删除节点的前驱
- 若
cur.val == valprev.next = cur.nextcur = cur.next
- 否则:
prev = curcur = cur.next
- 处理头节点
//删除所有出现的关键字节点
public void removeAll(int val) { //1. 判空 if (head == null) { return; } //2. 定义 prev 和 cur ListNode prev = head; ListNode cur = head.next; //3. 开始判断并删除 while (cur != null) { if (cur.val == val) { prev.next = cur.next; } else { prev = cur; } cur = cur.next; } //4. 处理头结点 if (head.val == val) { head = head.next; }
}
void clear()——清空
回收对象,防止内存浪费
head = null
//清空链表
public void clear() { head = null;
}
相关文章:
【数据结构】:用Java实现链表
在 ArrayList 任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为 O(n),效率比较低,因此 ArrayList 不适合做任意位置插入和删除比较多的场景。因此:java 集合中又引入了 LinkedList&…...
前端开发知识(三)-javascript
javascript是一门跨平台、面向对象的脚本语言。 一、引入方式 1.内部脚本:使用<script> ,可以放在任意位置,也可以有多个,一般是放在<body></body>的下方。 2.外部脚本:单独编写.js文件ÿ…...
Windows图形界面(GUI)-MFC-C/C++ - MFC绘图
公开视频 -> 链接点击跳转公开课程博客首页 -> 链接点击跳转博客主页 目录 MFC绘图 绘图基础 CPaintDC 实例代码 MFC绘图 绘图基础 设备上下文(Device Context, DC): 设备上下文是一个Windows GDI(图形设备接口)…...
51单片机-第五节-串口通信
1.什么是串口? 串口是通讯接口,实现两个设备的互相通信。 单片机自带UART,其中引脚有TXD发送端,RXD接收端。且电平标准为TTL(5V为1,0V为0)。 2.常见电平标准: (1)TTL电…...
【Linux常用命令】之df命令
Linux常用命令之df命令 文章目录 Linux常用命令之df命令常用命令之df背景介绍 总结 作者简介 听雨:一名在一线从事多年研发的程序员,从事网站后台开发,熟悉java技术栈,对前端技术也有研究,同时也是一名骑行爱好者。 D…...
2024年起重信号司索工(建筑特殊工种)证模拟考试题库及起重信号司索工(建筑特殊工种)理论考试试题
题库来源:安全生产模拟考试一点通公众号小程序 2024年起重信号司索工(建筑特殊工种)证模拟考试题库及起重信号司索工(建筑特殊工种)理论考试试题是由安全生产模拟考试一点通提供,起重信号司索工(建筑特殊工种)证模拟考试题库是根据起重信号司索工(建筑特…...
AWS全服务历史年表:发布日期、GA和服务概述一览 (全)
我一直在尝试从各种角度撰写关于Amazon Web Services(AWS)的信息和魅力。由于我喜欢技术历史,这次我总结了AWS服务发布的历史年表。 虽然AWS官方也通过“Whats New”发布了官方公告,但我一直希望能有一篇文章将公告日期、GA日期&…...
Leetcode 2824. 统计和小于目标的下标对数目
2824. 统计和小于目标的下标对数目 2824. 统计和小于目标的下标对数目 一、题目描述二、我的想法 一、题目描述 给你一个下标从 0 开始长度为 n 的整数数组 nums 和一个整数 target ,请你返回满足 0 < i < j < n 且 nums[i] nums[j] < target 的下标对…...
TCP服务器主动断开客户端
自定义消息函数 afx_msg LRESULT CbaseMFCprojectDlg::OnOnsocketbartender(WPARAM wParam, LPARAM lParam) WPARAM wParam:消息来源 res recv(wParam, cs, 65535, 0);获取这个客户端端口socket通道里面的信息长度为65535存放在cs里面 如果获取得到res0即是说明该客户端已经断…...
接口自动化中json.dumps()跟json.loads()区别详解
接口自动化中对于参数处理经常会用到json.dumps()跟json.loads(),下面主要分享一下自己使用总结 1.主要区别 json.dumps() 用于将字典转换为字符串格式 json.loads()用于将字符串转换为字典格式 import jsondict1 {"name":"amy","gender":woma…...
计算机网络-配置双机三层互联(静态路由方式)
目录 交换机工作原理路由器工作原理路由信息表组成部分路由器发决策 ARP工作原理配置双机三层互联(静态路由方式) 交换机工作原理 MAC自学习过程 初始状态: 刚启动的交换机的MAC地址表是空的。 学习过程: 当交换机收到一个数据帧…...
ES(Elasticsearch)常用的函数有哪些?
【电子书大全】内含上千本顶级编程书籍,是程序员必备的电子书资源包,并且会不断地更新,助你在编程的道路上更上一层楼! 链接: https://pan.baidu.com/s/1yhPJ9LmS_z5TdgIgxs9NvQ?pwdyyds > 提取码: yyds Elasticsearch&#x…...
【计算机网络】ICMP报文实验
一:实验目的 1:掌握ICMP报文的各种类型及其代码。 2:掌握ICMP报文的格式。 3:深入理解TTL的含义(Time to Live,生存时间)。 二:实验仪器设备及软件 硬件:RCMS-C服务器…...
transformers进行学习率调整lr_scheduler(warmup)
一、get_scheduler实现warmup 1、warmup基本思想 Warmup(预热)是深度学习训练中的一种技巧,旨在逐步增加学习率以稳定训练过程,特别是在训练的早期阶段。它主要用于防止在训练初期因学习率过大导致的模型参数剧烈波动或不稳定。…...
智能优化算法之灰狼优化算法(GWO)
智能优化算法是一类基于自然界中生物、物理或社会现象的优化技术。这些算法通过模拟自然界中的一些智能行为,如遗传学、蚁群觅食、粒子群体运动等,来解决复杂的优化问题。智能优化算法广泛应用于各种工程和科学领域,因其具有全局搜索能力、鲁…...
昇思25天学习打卡营第17天|计算机视觉
昇思25天学习打卡营第17天 文章目录 昇思25天学习打卡营第17天ShuffleNet图像分类ShuffleNet网络介绍模型架构Pointwise Group ConvolutionChannel ShuffleShuffleNet模块构建ShuffleNet网络 模型训练和评估训练集准备与加载模型训练模型评估模型预测 打卡记录 ShuffleNet图像分…...
Windows图形界面(GUI)-MFC-C/C++ - 键鼠操作
公开视频 -> 链接点击跳转公开课程博客首页 -> 链接点击跳转博客主页 目录 MFC鼠标 派发流程 鼠标消息(客户区) 鼠标消息(非客户) 坐标处理 客户区 非客户 坐标转换 示例代码 MFC键盘 击键消息 虚拟键代码 键状态 MFC鼠标 派发流程 消息捕获&#…...
Angular 18.2.0 的新功能增强和创新
一.Angular 增强功能 Angular 是一个以支持开发强大的 Web 应用程序而闻名的平台,最近发布了 18.2.0 版本。此更新带来了许多新功能和改进,进一步增强了其功能和开发人员体验。在本文中,我们将深入探讨 Angular 18.2.0 为开发人员社区提供的…...
matlab 小数取余 rem 和 mod有 bug
目录 前言Matlab取余函数1 mod 函数1.1 命令行输入1.2 命令行输出 2 rem 函数2.1 命令行输入2.2 命令行输出 分析原因注意 前言 在 Matlab 代码中mod(0.11, 0.1) < 0.01 判断为真,mod(1.11, 0.1) < 0.01判断为假,导致出现意料外的结果。 结果发现…...
Avalonia中的数据模板
文章目录 1. 介绍和概述什么是数据模板:数据模板的用途:2. 定义数据模板在XAML中定义数据模板:在代码中定义数据模板:3. 使用数据模板在控件中使用数据模板:数据模板选择器:定义数据模板选择器:在XAML中使用数据模板选择器:4. 复杂数据模板使用嵌套数据模板:使用模板绑…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
JavaSec-RCE
简介 RCE(Remote Code Execution),可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景:Groovy代码注入 Groovy是一种基于JVM的动态语言,语法简洁,支持闭包、动态类型和Java互操作性,…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...
STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...
网络编程(UDP编程)
思维导图 UDP基础编程(单播) 1.流程图 服务器:短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...
Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...
【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
