【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遍历
        //2
        for (int x : list){
            System.out.print(x+" ");
        }
(3)使用迭代器

正向遍历

        //3
        Iterator<Integer> iterator = list.listIterator();//传数据
        while (iterator.hasNext()){
            System.out.print(iterator.next()+" ");
        }
        System.out.println();

反向遍历

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FreeRTOS 源码结构解析与 STM32 HAL 库移植实践(任务创建、删除篇)

1. FreeRTOS源码结构介绍 1.1 下载源码 ​ 点击官网地址&#xff0c;选择 FreeRTOS 202212.01非 LTS 版本&#xff08;非长期支持版&#xff09;&#xff0c;因为这个版本有着最全的历程和更多型号处理器支持。 1.2 文件夹结构介绍 ​ 下载后主文件 FreeRTOSv202212.01 下包…

如何评估所选择的PHP后端框架的性能?

大家在选择PHP后端框架的时候&#xff0c;如果想评估其性能如何&#xff0c;能不能扛得住你的项目&#xff1f;可以根据以下几点进行分析&#xff0c;帮助大家选择到更符合自己心目中的PHP后端框架。 1. 基准测试 基准测试是评估框架性能的基础方法&#xff0c;主要通过模拟高…

家政预约小程序用例图分析

在和客户进行需求沟通的时候&#xff0c;除了使用常规的问答的形式&#xff0c;我还使用图形化工具更深入的沟通。比如借助UML的用例图来开展系统分析&#xff0c;并且按照角色详细拆解了家政预约小程序的各个用例。在分析阶段思考的越多&#xff0c;沟通的越多&#xff0c;在系…

Java里的ArrayList和LinkedList有什么区别?

大家好&#xff0c;我是锋哥。今天分享关于【Java里的ArrayList和LinkedList有什么区别&#xff1f;】面试题。希望对大家有帮助&#xff1b; Java里的ArrayList和LinkedList有什么区别&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 ArrayList 和 Lin…

【JavaSE-3】运算符

1、什么是运算符 就是对常量或者变量进行操作的符号&#xff0c;如&#xff1a;&#xff0c;-&#xff0c;*&#xff0c;/ 表达式&#xff1a; 用运算符把常量或者变量连接起来的&#xff0c;符合java语法的式子就是表达式。 2、 算术运算符 2.1、基本四则运算符 - * / % 都…

服务器配置-从0到分析4:ssh免密登入

该部分涉及到公钥、私钥等部分knowledge&#xff0c;本人仅作尝试 若将本地机器 SSH Key 的公钥放到远程主机&#xff0c;就能无需密码直接远程登录远程主机 1&#xff0c;在客户端生成 ssh 公私钥&#xff1a; 也就是我们本地机器&#xff0c;windows电脑 一路回车即可&am…

端到端自动驾驶——cnn网络搭建

论文参考&#xff1a;https://arxiv.org/abs/1604.07316 demo 今天主要来看一个如何通过图像直接到控制的自动驾驶端到端的项目&#xff0c;首先需要配置好我的仿真环境&#xff0c;下载软件udacity&#xff1a; https://d17h27t6h515a5.cloudfront.net/topher/2016/November…

SQL-labs13-16闯关记录

http://127.0.0.1/sqli-labs/less-13/ 基于POST单引号双注入变形 1&#xff0c;依然是一个登录框&#xff0c;POST型SQL注入 2&#xff0c;挂上burpsuite&#xff0c;然后抓取请求&#xff0c;构造请求判断漏洞类型和闭合条件 admin 发生了报错&#xff0c;根据提示闭合方式是(…

2025年渗透测试面试题总结- 阿某云安全实习(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 阿里云安全实习 一、代码审计经验与思路 二、越权漏洞原理与审计要点 三、SSRF漏洞解析与防御 四、教…

Zookeeper 及 基于ZooKeeper实现的分布式锁

1 ZooKeeper 1.1 ZooKeeper 介绍 ZooKeeper是一个开源的分布式协调服务&#xff0c;它的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来&#xff0c;构成一个高效可靠的原语集&#xff0c;并以一系列简单易用的接口提供给用户使用。 原语&#xff1a;操作系统或…

开放鸿蒙OpenHarmony 5.0.0 Release 兼容性测试实战经验分享

OpenHarmony 5.0版本的发布时间是2024年12月20日至21日。这个版本带来了许多新特性和改进。现在5.0出了两个release 版本&#xff0c;分别是5.0.0和5.0.1。 就在5.0版本发布不到2周的时间内&#xff0c;2025年01月01日起&#xff0c;不支持新产品基于老分支&#xff08;OpenHar…

DeepSeek开源周Day6:DeepSeek V3、R1 推理系统深度解析,技术突破与行业启示

DeepSeek 在开源周第六天再次发文&#xff0c;中文原文、官方号在知乎 DeepSeek - 知乎DeepSeek-V3 / R1 推理系统概览 - 知乎deepseek-ai/open-infra-index: Production-tested AI infrastructure tools for efficient AGI development and community-driven innovation 引言 …

springBoot集成emqx 实现mqtt消息的发送订阅

介绍 我们可以想象这么一个场景&#xff0c;我们java应用想要采集到电表a的每小时的用电信息&#xff0c;我们怎么拿到电表的数据&#xff1f;一般我们会想 直接 java 后台发送请求给电表&#xff0c;然后让电表返回数据就可以了&#xff0c;事实上&#xff0c;我们java应用发…

Docker安装milvus及其基本使用说明

简介 Milvus 是一款开源的高性能、高可用的向量数据库&#xff0c;专为大规模机器学习和深度学习应用设计&#xff0c;旨在高效管理和检索高维向量数据。随着AI技术的飞速发展&#xff0c;向量数据库在图像识别、语音识别、自然语言处理、推荐系统等领域扮演着越来越重要的角色…

ssm_mysql_小型企业人事管理系统

收藏关注不迷路&#xff01;&#xff01; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;还有大家在毕设选题&#xff08;免费咨询指导选题&#xff09;&#xff0c;项目以及论文编写等相关问题都可以给我留言咨询&#xff0c;希望帮助更多…

25年第四本【认知觉醒】

《认知觉醒》&#xff1a;一场与大脑的深度谈判 在信息爆炸的焦虑时代&#xff0c;我们像被抛入湍流的溺水者&#xff0c;拼命抓取各种自我提升的浮木&#xff0c;却在知识的漩涡中越陷越深。这不是一本简单的成功学指南&#xff0c;而是一场关于人类认知系统的深度对话&#…

盘古信息携手艾罗能源启动IMS数字化智能制造工厂项目,共筑新能源行业数字化标杆

在政策扶持下成长的新能源行业&#xff0c;如今已逐步进入商业化阶段。相比传统制造行业&#xff0c;新能源行业离散度高、自动化程度高。 面对迅疾的市场变化&#xff0c;在大环境中一枝独秀的新能源行业&#xff0c;亟需突破传统架构的限制&#xff0c;通过构建智能化生产体…

32.C++二叉树进阶1(二叉搜索树)

⭐上篇文章&#xff1a;31.C多态4&#xff08;静态多态&#xff0c;动态多态&#xff0c;虚函数表的存储位置&#xff09;-CSDN博客 ⭐本篇代码&#xff1a;c学习/18.二叉树进阶-二叉搜索树 橘子真甜/c-learning-of-yzc - 码云 - 开源中国 (gitee.com) ⭐标⭐是比较重要的部分…

图论基础算法: 二分图的判定(C++)

二分图的基本概念 什么是二分图? 二分图(Bipartite Graph)是指一个图的顶点集可以被分割为两个互不相交的子集 U U U 和 V V V, 并且图中的每一条边都连接 U U U 中的一个顶点和 V V V 中的一个顶点. 换句话说, 二分图中的顶点可以被分成两组, 组内的顶点之间没有边相连…

pyside6学习专栏(九):在PySide6中使用PySide6.QtCharts绘制6种不同的图表的示例代码

PySide6的QtCharts类支持绘制各种型状的图表&#xff0c;如面积区域图、饼状图、折线图、直方图、线条曲线图、离散点图等&#xff0c;下面的代码是采用示例数据绘制这6种图表的示例代码,并可实现动画显示效果&#xff0c;实际使用时参照代码中示例数据的格式将实际数据替换即可…