【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)头插法

主要操作:

  1. 将新创建的节点的指针域更改为头节点的地址 
  2. 将头节点的位置进行改变
public void addFirst(int data){//代码不能交换//必须先绑定信息ListNode listNode = new ListNode(data);listNode.next = head;head = listNode;}

 注意:代码的先后顺序不能颠倒,否则获取不到listNode.next的位置 

(2)尾插法

主要操作:

  1. 将最后一个节点的指针域指向新节点的地址 

特殊情况:

  1. 链表内没有一个节点,那么让新节点成为头节点

代码实现:

    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)固定位置插入

主要操作:

  1. 找到要插入位置的前一个位置(cur)
  2. 让新节点的指针域指向要插入位置的旧节点
  3. 让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)删除第一个出现的元素

主要步骤:

  1. 找到你要删除元素的前一个节点
  2. 找到你要删除的节点
  3. 进行删除操作

代码实现:

    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)删除出现的所有元素

主要步骤:

  1. 初始化两个节点,cur节点进行判断值是否相同,previous是cur节点的前一个结点,方便进行删除操作
  2. 进行遍历链表,找到相同的值,则进行删除操作,反之将两个节点都向后移动一位

代码实现:

        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 适合频繁插入和删除的场景,例如实现栈、队列,适合元素数量动态变化的场景。

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

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

相关文章

python-leetcode-不同的二叉搜索树 II

95. 不同的二叉搜索树 II - 力扣&#xff08;LeetCode&#xff09; # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.right right class S…

动态规划/贪心算法

一、动态规划 动态规划 是一种用于解决优化问题的算法设计技术&#xff0c;尤其适用于具有重叠子问题和最优子结构性质的问题。它通过将复杂问题分解为更简单的子问题&#xff0c;并保存这些子问题的解以避免重复计算&#xff0c;从而提高效率。 动态规划的核心思想 最优子结…

2月28日,三极管测量,水利-51单片机

众所周知&#xff0c;三极管&#xff08;BJT&#xff09;有三个管脚&#xff0c;基极&#xff08;B&#xff09;、集电极&#xff08;C&#xff09;、发射极&#xff08;E&#xff09;&#xff0c;在实际应用中&#xff0c;不可避免地会遇到引脚辨别的问题。接下来就讲下三极管…

Linux常见基本指令(二)

目录 1、Linux基础指令 文本查看 cat指令 more指令 less指令 head指令&tail指令 时间相关指令 查找、搜索相关指令 find指令 which指令 whereis指令 alias指令 grep指令 打包压缩和解压缩 zip指令&#xff08;压缩&#xff09; unzip&#xff08;解压&…

Day 55 卡玛笔记

这是基于代码随想录的每日打卡 所有可达路径 题目描述 ​ 给定一个有 n 个节点的有向无环图&#xff0c;节点编号从 1 到 n。请编写一个函数&#xff0c;找出并返回所有从节点 1 到节点 n 的路径。每条路径应以节点编号的列表形式表示。 输入描述 ​ 第一行包含两个整数…

2. 在后端代码中加入日志记录模块

1. 说明 日志模块基本上是每一个软件系统开发中必不可少的&#xff0c;主要用于持久记录一些代码运行中的输出信息&#xff0c;辅助编码人员进行代码调试&#xff0c;以及后期软件上线运行报错分析。在Python中加入日志模块比较简单&#xff0c;只需要借助logging和RotatingFi…

【Vue3】浅谈setup语法糖

Vue3 的 setup 语法糖是通过 <script setup> 标签启用的特性&#xff0c;它是对 Composition API 的进一步封装&#xff0c;旨在简化组件的声明式写法&#xff0c;同时保留 Composition API 的逻辑组织能力。以下是其核心概念和原理分析&#xff1a; 一、<script setu…

物联网小范围高精度GPS使用

在园区内实现小范围高精度GPS&#xff08;全球定位系统&#xff09;定位&#xff0c;通常需要结合多种技术来弥补传统GPS在精度和覆盖范围上的不足。以下是实现小范围高精度GPS定位的解决方案&#xff0c;包括技术选择、系统设计和应用场景。 一、技术选择 在园区内实现高精度…

【前端】前端设计中的响应式设计详解

文章目录 前言一、响应式设计的定义与作用二、响应式设计的原则三、响应式设计的实现四、响应式设计的最佳实践总结 前言 在当今数字化时代&#xff0c;网站和应用程序需要适应各种设备&#xff0c;从桌面电脑到平板电脑和手机。响应式设计应运而生&#xff0c;成为一种可以适…

Rocky Linux 系统安装 typecho 个人博客系统(Docker 方式)

typecho 博客系统安装 官网: https://typecho.org/ 1. 安装 Docker curl https://download.docker.com/linux/centos/docker-ce.repo -o /etc/yum.repos.d/docker.repo && yum install docker-ce -y && docker -v && systemctl enable --now docker…

pytorch-gpu版本安装(英伟达gpu驱动安装)

一、安装cuda 1️⃣ 检查是否有 GPU lspci | grep -i nvidia如果没有输出&#xff0c;可能你的服务器 没有 GPU&#xff0c;或者 GPU 未正确识别。 2️⃣ 检查 NVIDIA 驱动是否安装 dpkg -l | grep -i nvidia如果没有相关输出&#xff0c;说明驱动未安装&#xff0c;建议安…

华为OD-2024年E卷-分批萨[100分]

文章目录 题目描述输入描述输出描述用例1解题思路Python3源码 题目描述 吃货"和"馋嘴"两人到披萨店点了一份铁盘&#xff08;圆形&#xff09;披萨&#xff0c;并嘱咐店员将披萨按放射状切成大小相同的偶数个小块。但是粗心的服务员将披萨切成了每块大小都完全不…

【计算机网络入门】初学计算机网络(六)

目录 1.回忆数据链路层作用 2. 组帧 2.1 四种组帧方法 2.1.1 字符计数法 2.1.2 字节填充法 2.1.3 零比特填充法 2.1.4 违规编码法 3. 差错控制 3.1 检错编码 3.1.1 奇偶校验码 3.1.2 CRC&#xff08;循环冗余校验&#xff09;校验码 3.2 纠错编码 3.2.1 海明校验码…

yolo位姿估计实验

目录 介绍实验过程 2.1 数据集下载 2.2 模型和数据配置文件修改 2.3 模型训练参考链接 1. 介绍 1.1 简介 YOLOv8-Pose是基于YOLOv4算法的姿势估计模型&#xff0c;旨在实现实时高效的人体姿势估计。姿势估计在计算机视觉领域具有重要意义&#xff0c;可广泛应用于视频监控、…

极简Redis速成学习

redis是什么&#xff1f; 是一种以键值对形式存储的数据库&#xff0c;特点是基于内存存储&#xff0c;读写快&#xff0c;性能高&#xff0c;常用于缓存、消息队列等应用情境 redis的五种数据类型是什么&#xff1f; 分别是String、Hash、List、Set和Zset&#xff08;操作命…

大语言模型学习--本地部署DeepSeek

本地部署一个DeepSeek大语言模型 研究学习一下。 本地快速部署大模型的一个工具 先根据操作系统版本下载Ollama客户端 1.Ollama安装 ollama是一个开源的大型语言模型&#xff08;LLM&#xff09;本地化部署与管理工具&#xff0c;旨在简化在本地计算机上运行和管理大语言模型…

【OpenCV C++】以时间命名存图,自动检查存储目录,若不存在自动创建, 按下空格、回车、Q、S自动存图

文章目录 // 保存图像的函数 void saveImage(const cv::Mat& frame) {// 生成唯一文件名auto now = std::chrono::system_clock::

【JavaEE】线程安全

【JavaEE】线程安全 一、引出线程安全二、引发线程安全的原因三、解决线程安全问题3.1 synchronized关键字&#xff08;解决修改操作不是原子的&#xff09;3.1.1 synchronized的特性3.1.1 synchronized的使用事例 3.2 volatile 关键字&#xff08;解决内存可见性&#xff09; …

Vue核心知识:动态路由实现完整方案

在Vue中实现动态路由&#xff0c;并结合后端接口和数据库表设计&#xff0c;是一个复杂的项目&#xff0c;需要多个技术栈和步骤的配合。以下将详细描述整个实现过程&#xff0c;包括数据库设计、后端接口设计、前端路由配置以及如何实现动态路由的功能。 目录 一、需求分析二…

自媒体多账号如何切换不同定位才能做得更好

一、选择稀缺增长的赛道&#xff0c;避开内卷红海 1.职场赛道 ● 细分方向&#xff1a;公务员/体制内经验分享、自由职业指南、远程办公技巧。例如&#xff0c;通过采访自由职业者或分享远程工作体验&#xff0c;快速积累精准粉丝。 ● 优势&#xff1a;职场人群需求明确&…