【Java---数据结构】链表 LinkedList
1. 链表的概念
链表用于存储一系列元素,由一系列节点组成,每个节点包含两部分:数据域和指针域。
数据域:用于存储数据元素
指针域:用于指向下一个节点的地址,通过指针将各个节点连接在一起,形成一个链式结构。
注意:
链表在逻辑上是连续的,空间上并不是连续的
根据指针的连接方式,链表可以分为单向链表和双向链表
注意:
单向链表和双向链表的结点存在差异
2. 单向链表
单向链表是链表的一种,它的每个节点除了存储数据外,还包含一个指针指向下一个节点地址
2.1 单向列表的节点

每个节点只有一个指针,指向下一个节点。
最后一个节点的指针指向null,表示链表结束。
代码实现:
class ListNode{int val;ListNode next;//next指向新的节点public ListNode(int val) {this.val = val;}}
注意:
结点一般都是从堆上申请出来,每次在堆上申请的空间,按照一种策略来进行分配,多次申请出来的空间,可能连续,也可能不连续
2.2 链表的创建
1. 创建一个节点

2.创建第二个节点,让第一个节点的指针域存放第一个节点的地址,以此类推,可以创建出链表

代码实现:
public void createList(){//创建节点ListNode listNode = new ListNode(15);ListNode listNode1 = new ListNode(56);ListNode listNode2 = new ListNode(45);ListNode listNode3 = new ListNode(88);//节点连接listNode.next = listNode1;listNode1.next = listNode2;listNode2.next = listNode3;this.head = listNode;}
注意:
不要忘记标注链表的头节点,当输出头节点时,会输出整个链表的数据
2.3 链表的种类

2.4 链表的基本操作
2.4.1 增加
(1)头插法

主要操作:
- 将新创建的节点的指针域更改为头节点的地址
- 将头节点的位置进行改变
public void addFirst(int data){//代码不能交换//必须先绑定信息ListNode listNode = new ListNode(data);listNode.next = head;head = listNode;}
注意:代码的先后顺序不能颠倒,否则获取不到listNode.next的位置
(2)尾插法

主要操作:
- 将最后一个节点的指针域指向新节点的地址
特殊情况:
- 链表内没有一个节点,那么让新节点成为头节点
代码实现:
public void addLast(int data){ListNode listNode = new ListNode(data);ListNode cur = head;if(cur==null){head = listNode;return ;}while(cur.next!=null){//关注next的指向,找到最后一个节点cur = cur.next;}cur.next = listNode;}
(3)固定位置插入

主要操作:
- 找到要插入位置的前一个位置(cur)
- 让新节点的指针域指向要插入位置的旧节点
- 让cur指针域指向新节点的地址
注意:第二步和第三步不能交换位置,如果交换,会导致cur的位置发生改变,导致无法找到要插入位置的地址
代码实现:
public void addIndex(int index,int data){if(index<0||index>size()){System.out.println("位置有问题");return;}if(index == 0){addFirst(data);return;}if(index == size()){addLast(data);return ;}ListNode cur = head;int count = 0;ListNode listNode = new ListNode(data);while(count<index-1){cur =cur.next;count++;}//不能互换位置listNode.next = cur.next;cur.next = listNode;}
注意:
不要忘记检查,插入的位置是否合法
如果插入的位置为0;那么意味着头插法,位置为元素的长度,那么就是尾插法
2.4.2 删除
(1)删除第一个出现的元素
主要步骤:
- 找到你要删除元素的前一个节点
- 找到你要删除的节点
- 进行删除操作
代码实现:
public void remove(int data){if(head ==null){return;}if(head.val ==data){head = head.next;return;}//1.找到你要删除的前一个节点ListNode cur = head;int count = 0;while(cur.next!=null){if(cur.next.val == data){break;}count++;cur= cur.next;}//找到要删除的节点ListNode del = head;int delindex = 0;while (del!=null){if(del.val ==data){break;}delindex++;del = del.next;}//删除操作cur.next = del.next;}
注意:删除的具体操作就是:删除节点的前一个节点的指向发生改变,指向删除元素的指向

(2)删除出现的所有元素
主要步骤:
- 初始化两个节点,cur节点进行判断值是否相同,previous是cur节点的前一个结点,方便进行删除操作
- 进行遍历链表,找到相同的值,则进行删除操作,反之将两个节点都向后移动一位
代码实现:
if(head ==null){return;}ListNode cur = head.next;ListNode previous = head;while(cur!=null){if(cur.val==data){previous.next = cur.next;cur = cur.next;}else{previous = cur;//注意,小心写成 previous.next = cur;//错误cur = cur.next;}}if(head.val ==data){head = head.next;}}
注意:不要忘记对头节点进行判断
2.4.3 查看
(1)查看链表是否存在元素
public boolean contains(int value){ListNode cur = head;while(cur!=null){if(cur.val==value){return true;}cur = cur.next;}return false;}
主要步骤:
采用遍历的形式,如果找到元素,则返回true,反之,返回false
2.4.4 获取链表长度
int size(){int count = 0;ListNode cur = head;while (cur!=null){cur = cur.next;count++;}return count;}
2.4.5 清空链表
void clear(){ListNode cur = head;ListNode curNext ;while(cur!=null){curNext = cur.next;cur.next = null;cur = curNext;}head = null;}
}
注意: 遍历链表逐个释放节点的引用,让每个节点不再被引用
3. 快慢指针
思想:通过使用两个速度不同的指针(快指针和慢指针)来遍历数据结构,从而实现特定的功能
注意:本质就是利用指针的移动速度差异来解决问题
常见的解决情况:
(1)找出中间的节点
ListNode findListNode(Text_2 list){ListNode mid = head;if(head == null){return null;}if(head.next ==null){return head;}int count = size(list);int midCount = count/2;while (midCount>0){mid = mid.next;midCount--;}return mid;}
这是解决这种问题,我们第一个想到的方法,需要遍历两次链表,获取长度和中间节点 ,复杂度高,下面是我们引用了快慢指针:
ListNode findListNode1(){ListNode fast = head;ListNode slow = head;while(fast!=null&&fast.next!=null){//不能交换fast = fast.next.next;slow = slow.next;}return slow;}
核心思想:使用快慢双指针,快的是慢的二倍;那么快的到了,慢的一定就在中间
(2)找出倒数第k个节点
ListNode findListNode(int k){if(head ==null){return null;}if(k<=0||k>size()){System.out.println("k取值不对");return null;}ListNode slow = head;ListNode fast = head;int count = k-1;while (count>0){fast = fast.next;count--;}while(fast!=null&&fast.next!=null){fast =fast.next;slow =slow.next;}return slow;}
核心思想:快的比慢的先走了k-1个,然后每次都移动一个, 那么快的到了,满的就是倒数第k个.
先走k-1个,因为下标从0开始
4. 双向链表
双向链表是链表的一种,它的每个节点除了存储数据外,还包含两个指针:一个指向前一个节点,另一个指向下一个节点
4.1 双向列表的节点

注意:
由于有前驱指针,删除和插入操作更高效。
每个节点需要额外存储一个指针,因此比单向链表占用更多内存。
可以从头节点向后遍历,也可以从尾节点向前遍历。
代码实现:
class ListNode{int val;ListNode prev;ListNode next;public ListNode(int val) {this.val = val;}
}
4.2 LinkedList
在 Java 中,LinkedList是java.util 包中的一个类,LinkedList的底层使用了双向链表
4.3 LinkedList的使用
4.3.1 LinkedList的构造
(1)无参构造
//调用无参构造List<Integer> list =new LinkedList<>();
(2)利用Collection构建
List<Integer> list1 =new LinkedList<>();list1.add(1);list1.add(2);list1.add(3);System.out.println(list1);List<Integer> list2 = new LinkedList<>(list1);list2.add(2);System.out.println(list2);
注意:会继承传入实例对象的所有元素,并且元素的顺序也会保持一致
4.3.2 LinkedList的常用API
| boolean add(E e) | 尾插 |
| void add(int index, E element) | 在index位置添加元素 |
| boolean addAll(Collection<? extends E> c) | 将c中的所有元素插入进来 |
| E remove(int index) | 删除index下标的元素 |
| boolean remove(Object o) | 删除一个o元素 |
| E get(int index) | 获得index下标的元素 |
| E set(int index, E element) | 将index下标的元素更改为element |
| void clear() | 清空顺序表 |
| boolean contains(Object o) | 查看是否有元素o |
| int indexOf(Object o) | 获取第一个元素o的下标 |
| int lastIndexOf(Object o) | 获取最后一个元素o的下标 |
| List<E> subList(int fromIndex, int toIndex) | 截取顺序表的一部分 |
(1)增加
public class Add {public static void main(String[] args) {//尾插法LinkedList<Integer> list =new LinkedList<>();list.add(1);list.add(2);list.add(3);System.out.println(list);//插入固定位置LinkedList<Integer> list1 = new LinkedList<>();list1.add(0,1);list1.add(0,6);list1.add(1,9);System.out.println(list1);//尾插所有元素Integer[] array = {9,99,999};list.addAll(Arrays.asList(array));System.out.println(list);}
}
(2)删除
public class Remove {public static void main(String[] args) {LinkedList<Integer> list = new LinkedList<>();list.add(1);list.add(2);list.add(3);//删除下标的数list.remove(2);System.out.println(list);//删除第一个出现的数list.remove(new Integer(2));System.out.println(list);}
}
(3)修改
public class Set {public static void main(String[] args) {LinkedList<Integer> list = new LinkedList<>();list.add(1);list.add(2);list.add(3);list.set(1,999);System.out.println(list);}
}
(4)获取
public class Get {public static void main(String[] args) {LinkedList<Integer> list = new LinkedList<>();list.add(1);list.add(2);list.add(3);int x = list.get(2);System.out.println(x);}
}
(5)清空
public class Clear {public static void main(String[] args) {LinkedList<Integer> list = new LinkedList<>();list.add(1);list.add(2);list.add(3);list.clear();System.out.println(list);}
}
(6)查找
public class Contains {public static void main(String[] args) {LinkedList<Integer> list = new LinkedList<>();list.add(1);list.add(2);list.add(3);
// 判断元素是否在表中Boolean x = list.contains(2);System.out.println(x);
// 返回第一个元素所在的下标int a = list.indexOf(2);System.out.println(a);
// 返回最后一个元素所在的下标int b = list.lastIndexOf(2);System.out.println(b);}
}
(7)截取
public class Sublist {public static void main(String[] args) {LinkedList<Integer> list = new LinkedList<>();list.add(1);list.add(2);list.add(3);list.add(6);list.add(9);// LinkedList<Integer> list1 = new LinkedList<>(list.subList(1,4));//创建新的对象,进行操作,不会影响原来列表的数据//subList返回值是List类型List<Integer> list1 = list.subList(1,4);list1.add(999);list1.add(888);list1.set(0,111);System.out.println(list1);System.out.println(list);}
}
4.3.3 LinkedList的遍历
(1)for循环遍历
//1.for (int i = 0; i < list.size(); i++) {System.out.print(list.get(i)+" ");}System.out.println();
(2)for-each遍历
//2for (int x : list){System.out.print(x+" ");}
(3)使用迭代器
正向遍历
//3Iterator<Integer> iterator = list.listIterator();//传数据while (iterator.hasNext()){System.out.print(iterator.next()+" ");}System.out.println();
反向遍历
//4ListIterator<Integer> listIterator = list.listIterator(list.size());while (listIterator.hasPrevious()){System.out.print(listIterator.previous()+" ");}System.out.println();
注意:
(从前往后)while循环中的判断条件表示:是否存在下一个元素,如果存在,则获取迭代器的下一个元素并输出,因为 next 方法,所以在每次调用后进行移动1位
5. ArrayList和LinkedList的区别
| 差别 | ArrayList | LinkedList |
| 底层实现 | 基于动态数组实现 | 基于双向链表实现 |
| 访问效率 | 随机访问效率高 | 随机访问效率低 |
| 插入和删除效率 | 尾部插入和删除效率高 中间或头部插入和删除效率低 | 任意位置插入和删除效率高 |
| 内存占用 | 内存占用较少,因为只需要存储数据和数组容量。 可能存在内存浪费,因为数组会预留一定的容量 | 内存占用较多,因为每个节点需要存储数据以及前后指针。 |
总结:
ArrayList 适合频繁访问元素的场景,例如随机读取数据,适合元素数量相对固定的场景。
LinkedList 适合频繁插入和删除的场景,例如实现栈、队列,适合元素数量动态变化的场景。
相关文章:
【Java---数据结构】链表 LinkedList
1. 链表的概念 链表用于存储一系列元素,由一系列节点组成,每个节点包含两部分:数据域和指针域。 数据域:用于存储数据元素 指针域:用于指向下一个节点的地址,通过指针将各个节点连接在一起,形…...
紧跟 Web3 热潮,RuleOS 如何成为行业新宠?
Web3 热潮正以汹涌之势席卷全球。从金融领域的创新应用到供应链管理的变革,从社交媒体的去中心化尝试到游戏产业的全新玩法探索,Web3 凭借其去中心化、安全性和用户赋权等特性,为各个行业带来了前所未有的机遇。在这股热潮中,Rule…...
CC++的内存管理
目录 1、C/C内存划分 C语言的动态内存管理 malloc calloc realloc free C的动态内存管理 new和delete operator new函数和operator delete函数 new和delete的原理 new T[N]原理 delete[]的原理 1、C/C内存划分 1、栈:存有非静态局部变量、函数参数、返回…...
Spark核心之02:RDD、算子分类、常用算子
spark内存计算框架 一、目标 深入理解RDD弹性分布式数据集底层原理掌握RDD弹性分布式数据集的常用算子操作 二、要点 ⭐️1. RDD是什么 RDD(Resilient Distributed Dataset)叫做**弹性分布式数据集,是Spark中最基本的数据抽象,…...
【Resis实战分析】Redis问题导致页面timeout知识点分析
事故现象:前端页面返回timeout 事故回溯总结一句话: (1)因为大KEY调用量,随着白天自然流量趋势增长而增长,最终在业务高峰最高点期占满带宽使用100%。   (2&#x…...
单一职责原则(设计模式)
目录 问题: 定义: 解决: 方式 1:使用策略模式 示例:用户管理 方式 2:使用装饰者模式 示例:用户操作 方式 3:使用责任链模式 示例:用户操作链 总结 推荐 问题&a…...
生理信号概念
rPPG 信号(远程光电容积脉搏波信号) 原理: 基于光电容积脉搏波描记法,利用普通摄像头,在一定距离外捕捉人体皮肤表面因心脏泵血导致的血液容积变化引起的细微颜色变化,通过图像处理和信号分析算法提取心率…...
安卓内存泄露之DMA-BUF异常增长:Android Studio镜像引起DMA内存泄露
安卓内存泄露之DMA-BUF异常增长:Android Studio镜像引起DMA内存泄露 - Wesley’s Blog 今天用着安卓 14 的板子的时候突然系统卡死。 查看日志发现launcher都被干掉了 03-04 06:13:35.544 7872 8479 I ActivityManager: vis BFGS 18740: com.android.launcher3 (pid 8407) se…...
android13打基础: 控件checkbox
测试checkbox的activity // todo: 高级控件checkbox public class Ch4_CheckBoxActivity extends AppCompatActivityimplements CompoundButton.OnCheckedChangeListener {Overrideprotected void onCreate(Nullable Bundle savedInstanceState) {super.onCreate(savedInstance…...
AI应用测试:遇到类ChatGPT的流式接口要如何压测?
先说结论: 使用最普遍的JMeter 就能支持类 OpenAI 的流式接口(如 ChatGPT 的流式聊天接口)的测试 总体设置 JMeter 支持测试 OpenAI 的流式接口,但需要额外配置(如启用 KeepAlive 和调整超时)。如果需要实时处理流式响应,使用 Regular Expression Extractor 或自定义脚…...
React面试葵花宝典之二
36.Fiber的更新机制 React Fiber 更新机制详解 React Fiber 是 React 16 引入的核心架构重构,旨在解决可中断渲染和优先级调度问题,提升复杂应用的流畅性。其核心思想是将渲染过程拆分为可控制的工作单元,实现更细粒度的任务管理。以下是其…...
在日常生活、工作中deepseek能帮我们解决哪些问题
在日常生活、工作中deepseek能帮我们解决哪些问题 DeepSeek极大降低了普通人使用AI的门槛,让AI快速渗透到人们的工作和生活中,无论是专业场景提效、教育学术赋能、商业创新甚至日常生活,都变得更加轻松。 当然这篇文章也参考了deepseek的回…...
【Java】IO流
Java IO流是Java中处理输入输出的核心机制,通过不同的流类型实现了对数据的高效读写。 一、IO流的分类 1. 按数据方向 输入流(Input Stream):从数据源(如文件、网络等)读取数据。输出流(Outp…...
HTML第三节
一.初识CSS 1.CSS定义 A.内部样式表 B.外部样式表 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title&g…...
Visual Studio 2022安装问题解决,提示无法安装Microsoft.VisualStudio.Community.Msi
表现现象为:安装完后提示无法安装Microsoft.VisualStudio.Community.Msi,无法正常开发C项目 查看日志,大概显示: xxx ReturnCode1316 xxxxx 消息详细信息: 指定的帐户已存在。 试了网上所有的办法都没用,反复尝试&…...
【代码分享】基于IRM和RRT*的无人机路径规划方法详解与Matlab实现
基于IRM和RRT*的无人机路径规划方法详解与Matlab实现 1. IRM与RRT*的概述及优势 IRM(Influence Region Map)通过建模障碍物的影响区域,量化环境中的安全风险,为RRT算法提供启发式引导。RRT(Rapidly-exploring Random…...
MybatisPlus从入门到精通
一、MyBatis-Plus核心特性 无侵入性 在MyBatis基础上增强,无需修改原有代码即可使用。自动化CRUD 内置通用Mapper和Service,减少80%单表操作代码。Lambda表达式 支持Lambda形式的条件构造,避免字段名硬编码错误。主键策略 支持雪花算法&…...
el-table input textarea 文本域 自适应高度,切换分页滚动失效处理办法
场景: el-table 表格 需要 input类型是 textarea 高度是自适应,第一页数据都是单行数据 不会产生滚动条,但是第二页数据是多行数据 会产生滚动条, bug: 第一页切换到第二页 第二页滚动条无法展示 解决办法:直接修改样…...
基于Windows11的DockerDesktop安装和布署方法简介
基于Windows11的DockerDesktop安装和布署方法简介 一、下载安装Docker docker 下载地址 https://www.docker.com/ Download Docker Desktop 选择Download for Winodws AMD64下载Docker Desktop Installer.exe 双点击 Docker Desktop Installer.exe 进行安装 测试Docker安装是…...
ffmpeg源码编译支持cuda
1.安装cuda CUDA Toolkit 11.3 Downloads | NVIDIA Developer 在选择组件的时候,将CUDA中的Nsight VSE和Visual Studio Integration取消勾选 不然会安装失败 2.编译ffmpeg 把cuda编译宏定义开启,再编译avcodec 3.编译livavutil报错struct "Cuda…...
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
srs linux
下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935,SRS管理页面端口是8080,可…...
JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...
汇编常见指令
汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX(不访问内存)XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...
蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...
视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...
【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...
