LinkedList和链表(下)

1. 什么是LinkedList

        在练习了单链表的自我实现和单链表的一些习题之后,我们正式来认识一下java提供的LinkedList,这是一种双向链表结构,在增删元素的时候效率比较高,不需要像ArrayList一样搬运元素.但是在查找方面效率比较低(需要遍历链表),ArrayList效率就比较高(直接由数组下标访问).

我们来看一下它底层接口实现

从上图我们可以看出

1. LinkedList实现了List接口

2. LinkedList的底层使用了双向链表

3. LinkedList底层没有实现RandomAccess接口,因此LinkedList不支持随机访问

4. LinkedList的任意位置插入和删除元素效率比较高,时间复杂度为O(1)

5. LinkedList比较适合在任意位置插入的场景

 2. 实现一个自己的双向链表

为了更好的了解双向链表LinkedList,我们自己来实现一个.

        先来认识一下Ilist接口,一下是它的抽象方法.

       

        然后我们写一个MyLinkedList实现依次实现Ilist接口,然后后续我们依次重写每一个方法.在此之前我们先把结点内部类先构造好,链表是一个大的整体,而结点是其中的小整体,因此我们就用内部类来表示结点.每个结点的属性是由val,next,prev组成,val里面放置的是我们结点的值,next放置的是下一个结点的地址,prev放置的是上一个结点的地址.然后我们提供一个构造方法,每次创建一个结点的适合我们就需要把val放进去. 然后我们要单独把head和last创建出来.

        好了,我们正式来介绍重写方法

        2.1 遍历打印元素display()

                我们需要遍历整个链表,并且打印每一个结点的值,我们先定义一个cur让它指向head,然后我们开始遍历,因为我们需要把每一个结点遍历到,因此我们循环条件是cur != null;循环内部,我们每一次循环就需要打印val并且把cur重置一下.让他指向它的下一个.

        2.2 判断是否包含某个元素contains(int key)

        我们判断某个元素是否在链表里面,我们就应该先遍历这个链表,每次遍历就判断cur.val是否和key值一样.如果不一样就把cur继续更新为下一个结点.

                

        2.3 头插法addFirst(int data)

                双向链表进行头插法,如图,我们只需要让node指向head,修改head.prev,和修改node.next即可.不过我们在进行头插法之前,先要对head进行判断.看它是不是空的,是我们就直接把head和last指向node即可.

           2.4 尾插法addLast(int data)

                此刻双向链表,我们不需要像单链表一样先找到尾巴结点,因为,我们本身就由一个last指向尾巴结点,我们直接修改即可.同时我们也要考虑head是否为null的情况,如果为null,我们直接让head和last指向node即可

                2.5 插入到任意位置addIndex(int index,int data)

                我们要在提供的index位置来插入元素,因为提供了下标,我们需要对下标进行合法性判断,首先判断它是否越界,然后判断是不是在第一个结点插,是的话就进行头插法,判断是不是在最后一个结点插,是的话就进行尾插法.不然的话就都是中间结点的插入,我们先找到index位置的结点,我们创建一个私有方法findIndex(int index)找到我们的index位置上的结点.

        我们找到结点之后,先让node结点和前后俩个结点进行相联,node.next = cur;node.prev = cur.prev,然后我们再修改上一个节点的next和当前节点的prev,cur.prev.next = node;cur.prev = node;

                        2.6 删除给定值节点remove(int key)

        我们删除给定值节点,必然是要遍历这个链表的,再在这个基础上进行删除,然后我们要分三种情况来进行讨论,因为如果我们不考虑头和尾的情况,假设我们删除的是最后一个节点cur.next就是空指针异常了.如果是头节点,我们cur.prev.next就会空指针异常.此时我们先讨论尾巴和中间的情况,如果是尾巴的话,我们直接让last = last.prev;并且把last.next设置为null.如果是中间,我们就通过cur作为桥梁进行删除,cur指向的就是我们当前要删除的节点,我们只需要让cur的前一个指向cur的后一个节点即可,也就是cur.prev.next = cur.next;cur.next.prev = cur.prev;

        

        然后我们讨论头节点的情况,我们头节点,这个情况就是又要分为只有头节点一个和除了头节点还有其他节点的情况,我们先来讨论除了头节点还有其他节点的情况,我们直接让head = head.next ,并且把head.prev = null;即可,而如果只有头节点一个,我们再进行head.prev操作的时候会空指针异常,因为我们的head此时为null,不能再操作它了.因此我们直接head = head.next 即可

                       

                2.7 删除所有的给定值节点removeAll(int key)

        删除所有的key,我们只需要在刚刚的基础上改一下删除尾巴节点的写法即可,我们让cur来进行操作,如果删除的是尾巴节点cur.prev.next = cur.next(此时cur.next为null),last = last.next;删除的是中间节点,cur.prev.next = cur.next;cur.next.prev=cur.prev;此时可以合并一下,如图,最后我们在每次判断完之后就让cur = cur.next ,遍历完整个链表,删除全部的key为止.

                

                2.8 清空整个链表clear()

        我们清空整个链表,需要把val值(如果是引用类型)置为空,然后我们要把每一个节点的prev,next都置为null,把head和last也置为null.我们先创建cur让它指向head,然后对链表进行遍历,我们把val设置为null(若为引用类型),我们创建temp让他指向cur的下一个节点,然后我们操作cur,让cur把它的prev和next置为空,然后我们把cur更新为tmp,最后我们要手动置空head和last.

                整体代码:

package LinkedList和链表;

import java.util.List;

public class MyLinkedList implements Ilist{
    static class ListNode {
        public int val;
        public ListNode next;
        public ListNode prev;

        public ListNode(int val) {
            this.val = val;
        }
    }
    //last指向尾巴,head指向头
    public ListNode head;
    public ListNode last;
//头插法
    @Override
    public void addFirst(int  data) {
        ListNode node = new ListNode(data);
        //如果链表是空的
        if (head == null) {
            head = node;
            last = node;
        }else {
            //如果链表不为空
            node.next = head;
            head.prev = node;
            head = node;
        }
    }

    @Override
    public void addLast(int data) {
        ListNode node = new ListNode(data);
        if(head == null) {
            last = node;
            head = node;
        }else {
            last.next = node;
            node.prev = last;
            last = node;
        }
    }

    @Override
    public void addIndex(int index, int data) throws IndexIlleage{
        ListNode node = new ListNode(data);
        //检擦Index是否合法
        int len = size();
        if(index < 0 || index > len) {
            throw new IndexIlleage("下标越界! " + index);
        }
        if(index == 0) {
            //头插
            addFirst(data);
            return;
        }
        if(index == len) {
            //尾插
            addLast(data);
            return;
        }
        //找到cur,cur走index步
        ListNode cur = findIndex(index);
        //先修改next再修改prev
        node.next = cur;
        node.prev = cur.prev;
        cur.prev.next = node;
        cur.prev = node;
    }
    private ListNode findIndex(int index) {
         ListNode cur = head;
         while (index != 0) {
             cur = cur.next;
             index--;
         }
         return cur;
    }

    @Override
    public boolean contains(int key) {
        ListNode cur = head;
        while (cur != null) {
         if(cur.val == key) {
             return true;
         }
            cur = cur.next;
        }
        return false;
    }
    //TODO 删除

    @Override
    public void remove(int key) {
        ListNode cur = head;
        //先找到那个和Key相等的结点
        while (cur != null) {
            //删除的是头
            if (head.val == key) {
                head = head.next;
                //如果只有一个结点的话,继续操作head会空指针异常
                if (head != null) {
                    head.prev = null;
                    return;
                }
                last = null;//如果只有一个结点的话head和last都得置为空
                return;
            }
            //删除的是尾巴
            if (last.val == key) {
                last = last.prev;
                last.next = null;
                return;
            }
            if (cur.val == key) {
                cur.prev.next = cur.next;
                cur.next.prev = cur.prev;
            } else {
                cur = cur.next;
            }
        }
    }


//TODO 删除所有的key值
    @Override
    public void removeAllKey(int key) {
        ListNode cur = head;
        //先找到那个和Key相等的结点
        while (cur != null) {
            if (cur.val == key) {
                if (cur == head) {//删除的是头
                    head = head.next;
                    if (head == null) {
                        last = null;//删除链表只有一个结点
                    } else {
                        head.prev = null;//删除的链表不止一个结点
                    }
                } else {
                    cur.prev.next = cur.next;
                    if (cur.next == null) {//删除的是尾巴
                        last = last.prev;
                        last.next = null;
                    } else {
                        cur.next.prev = cur.prev;//删除的是中间结点
                    }
                }
            }
            cur = cur.next;
        }
    }

    @Override
    public int size() {
        ListNode cur = head;
        int count = 0;
        while (cur != null) {
            count++;
            cur = cur.next;
        }
        return count;
    }
//TODO 清空
    @Override
    public void clear() {
//    head = null;
//    last = null;//比较暴力,也能回收
        ListNode cur = head;
        while (cur != null) {
//            cur.val = null;引用数据类型
          ListNode tmp = cur.next;
          cur.prev = null;
          cur.next = null;
          cur = tmp;
        }
        //我们把 head 和 last 手动置为空
        head = null;
        last = null;
    }

    @Override
    public void display() {
    ListNode cur = head;
    while (cur != null) {
        System.out.print(cur.val + " ");
        cur = cur.next;
    }
        System.out.println();
    }
}

      3. Java底层的LinkedList 的使用

           3.1 构造方法

方法解释
LinkedList()无参构造
public LinkedList(Collection<?extendsE>c)使用其他集合容器中元素构造List

     

我们主要来看看第二个构造方法:Collection<? extends E > c

        1. 该集合类必须实现Collection接口

        2. 在第一个<>设置的类型必须是第二个<>的子类或者它自己

这样就能实现直接把另一个实例的内容作为我新的实例的内容.

我们直接看下面的例子即可.

        3.2 LinkedList其他方法的介绍和使用

                

方法解释
boolean add(E e)尾插e
void add(int index,E element)把e插入e位置
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)截取部分list

我们来使用一下:

package LinkedList和链表;

import org.omg.PortableInterceptor.INACTIVE;

import java.util.LinkedList;
import java.util.List;

public class t1 {
    public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<>();
        list.add(1); // add(elem): 表示尾插
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        list.add(6);
        list.add(7);
        System.out.println(list.size());
        System.out.println(list);
// 在起始位置插入0
        list.add(0, 0); // add(index, elem): 在index位置插入元素elem
        System.out.println(list);
        list.remove(); // remove(): 删除第一个元素,内部调用的是removeFirst()
        list.removeFirst(); // removeFirst(): 删除第一个元素
        list.removeLast(); // removeLast(): 删除最后元素
        list.remove(1); // remove(index): 删除index位置的元素
        System.out.println(list);
// contains(elem): 检测elem元素是否存在,如果存在返回true,否则返回false
        if (!list.contains(1)) {
            list.add(0, 1);
        }
        list.add(1);
        System.out.println(list);
        System.out.println(list.indexOf(1)); // indexOf(elem): 从前往后找到第一个elem的位置
        System.out.println(list.lastIndexOf(1)); // lastIndexOf(elem): 从后往前找第一个1的位置
        int elem = list.get(0); // get(index): 获取指定位置元素
        list.set(0, 100); // set(index, elem): 将index位置的元素设置为elem
        System.out.println(list);
// subList(from, to): 用list中[from, to)之间的元素构造一个新的LinkedList返回
        List<Integer> copy = list.subList(0, 3);
        System.out.println(list);
        System.out.println(copy);
        list.clear(); // 将list中元素清空
        System.out.println(list.size());
    }
}

结果:

3.3 遍历

        我们LinkedList有很多种遍历方式.

1.直接打印链表名字(因为实现了toString方法)

2.使用for-each

3.使用for循环

4.使用迭代器(也可以逆着打)

我们直接看代码

public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        System.out.println(list);
        System.out.println("=======");
        for(Integer x:list){
            System.out.print(x+" ");
        }
        System.out.println();
        System.out.println("=========");
        for (int i = 0;i < list.size();i++){
            System.out.print(list.get(i)+" ");
        }
        System.out.println();
        System.out.println("==============");
        //迭代器
        Iterator<Integer>  it = list.iterator();
        while (it.hasNext()) {
            System.out.print(it.next()+" ");
        }
        System.out.println();
       ListIterator<Integer> it1 =  list.listIterator();
       while (it.hasNext()) {
           System.out.println(it1.next());
       }
       //TODO 使用迭代器反向打印
        System.out.println("反向");
       ListIterator<Integer> it2 = list.listIterator(list.size());
       while (it2.hasPrevious()) {
           System.out.print(it2.previous()+" ");
       }
        System.out.println();

运行结果:

4. ArrayList和LinkedList的区别

这个是个面试题,我们可以有以下三种问法:

1. ArrayList和LinkedList区别是什么?

2. 顺序表和链表(分双向和单向)的区别?

3. 数组和连边的区别是什么?

如果时经常根据下标进行查找使用顺序表ArrayList,如果经常进行插入和删除操作的可以使用链表LinkedList.下面是更详细的回答

不同点ArrayListLinkedList
存储空间上物理上一定连续逻辑上连续,物理上不一定连续
随机访问O(1)O(n)
头插需要搬运元素,O(n)只需要改变引用指向,O(1)
插入空间不顾时需要扩容没有容量概念
应用场景元素高效存储+频繁访问任意位置频繁插入和删除

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

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

相关文章

JS+Springboot做一个交互Demo

背景&#xff1a;老大做了一个 SDK&#xff0c;包含字符加解密、文件加解密&#xff0c;要求能从前端访问&#xff0c;并且能演示的 Demo。 思路&#xff1a;html 写页面&#xff0c;js 发送请求&#xff0c;freemarker 做简单的参数交互&#xff0c;JAVA 后端处理。 一、项目依…

CSS 样式 box-sizing: border-box; 用于控制元素的盒模型如何计算宽度和高度

文章目录 box-sizing: border-box; 的含义默认盒模型 (content-box)border-box 盒模型 在微信小程序中的应用示例 在微信小程序中&#xff0c;CSS 样式 box-sizing: border-box; 用于控制元素的盒模型如何计算宽度和高度。具体来说&#xff0c; box-sizing: border-box; 会改…

【已解决】C# NPOI如何在Excel文本中增加下拉框

前言 上图&#xff01; 解决方法 直接上代码&#xff01;&#xff01;&#xff01;&#xff01;综合了各个大佬的自己修改了一下&#xff01;可以直接规定在任意单元格进行设置。 核心代码方法块 #region Excel增加下拉框/// <summary>/// 增加下拉框选项/// </s…

Python游戏开发超详细(基础理论知识篇)

一、引导&#xff1a; Python游戏开发是一个非常有趣且富有挑战性的领域。通过Python&#xff0c;你可以利用其强大的库和框架来创建各种类型的游戏&#xff0c;从简单的2D游戏到复杂的3D游戏。以下是第一课的基础理论知识&#xff0c;帮助你入门Python游戏开发。 二、理论知识…

中小企业设备资源优化:Spring Boot系统实现

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

部署seatunnel2.3.8

部署seatunnel web参考&#xff1a;SeaTunnel Web1.0.0安装_plugindiscoveryutil.getallconnectors-CSDN博客 配置&#xff1a;两台centos服务器&#xff0c;2master2worker 一、下载包 v2.3.8[bin] apache-seatunnel-2.3.8-bin.tar.gz 将包上传到master节点和worker节点所…

Python开发日记 -- 实现bin文件的签名

目录 1.数据的不同表现形式签名值不一样&#xff1f; 2.Binascii模块简介 3.问题定位 4.问题总结 1.数据的不同表现形式签名值不一样&#xff1f; Happy Muscle试运行了一段时间&#xff0c;组内同事再一次提出了新的需求&#xff1a;需要对bin文件签名。 PS&#xff1a;服…

使用代码编辑组件的npm包

使用代码编辑组件的npm包 文章说明核心代码运行截图源码下载 文章说明 我将书写的代码编辑组件打包为npm包&#xff0c;下载即可使用&#xff0c;目前是1.0.4版本&#xff0c;虽然功能还有一些bug&#xff0c;但是可以较为简单的使用 npm地址 核心代码 安装依赖 npm i bingbing…

H7-TOOL的LUA小程序教程第16期:脉冲测量,4路PWM,多路GPIO和波形打印(2024-10-25, 更新完毕)

LUA脚本的好处是用户可以根据自己注册的一批API&#xff08;当前TOOL已经提供了几百个函数供大家使用&#xff09;&#xff0c;实现各种小程序&#xff0c;不再限制Flash里面已经下载的程序&#xff0c;就跟手机安装APP差不多&#xff0c;所以在H7-TOOL里面被广泛使用&#xff…

OpenCV-物体跟踪

文章目录 一、物体跟踪的定义二、OpenCV中的物体跟踪算法三、OpenCV物体跟踪的实现步骤四、代码实现五、注意事项 OpenCV是一个开源的计算机视觉和机器学习软件库&#xff0c;它提供了丰富的功能来实现物体跟踪。以下是对OpenCV中物体跟踪的详细解释&#xff1a; 一、物体跟踪的…

清华大学《2022年+2021年822自动控制原理真题》 (完整版)

本文内容&#xff0c;全部选自自动化考研联盟的&#xff1a;《清华大学822自控考研资料》的真题篇。后续会持续更新更多学校&#xff0c;更多年份的真题&#xff0c;记得关注哦~ 目录 2022年真题 2021年真题 Part1&#xff1a;2022年2021年完整版真题 2022年真题 2021年真题…

论文笔记:通用世界模型WorldDreamer

整理了WorldDreamer: Towards General World Models for Video Generation via Predicting Masked Tokens 论文的阅读笔记 背景模型实验 背景 现有的世界模型仅限于游戏或驾驶等特定场景&#xff0c;限制了它们捕捉一般世界动态环境复杂性的能力。针对这一挑战&#xff0c;本文…

【若依笔记】-- 精简若依项目只保留系统管理

环境&#xff1a;最近项目需要计划使用若依来开发软件&#xff0c;使用若依有一个问题&#xff0c;若依代码框架还是比较冗余&#xff0c;不够精简&#xff0c;还有一点是若依Security权限校验&#xff0c;对于实现一对多的前台&#xff0c;比较麻烦&#xff0c;我这边的业务是…

大一物联网要不要转专业,转不了该怎么办?

有幸在2014年&#xff0c;踩中了物联网的风口&#xff0c;坏消息&#xff0c;牛马的我&#xff0c;一口汤都没喝上。 依稀记得&#xff0c;当时市场部老大&#xff0c;带我去上海参加电子展会&#xff0c;印象最深的&#xff0c;一些物联网云平台&#xff0c;靠着一份精美PPT&a…

【Python爬虫系列】_031.Scrapy_模拟登陆中间件

课 程 推 荐我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈虚 拟 环 境 搭 建 :👉👉 Python项目虚拟环境(超详细讲解) 👈👈PyQt5 系 列 教 程:👉👉 Python GUI(PyQt5)教程合集 👈👈…

接口测试(八)jmeter——参数化(CSV Data Set Config)

一、CSV Data Set Config 需求&#xff1a;批量注册5个用户&#xff0c;从CSV文件导入用户数据 1. 【线程组】–>【添加】–>【配置元件】–>【CSV Data Set Config】 2. 【CSV数据文件设置】设置如下 3. 设置线程数为5 4. 运行后查看响应结果

Linux 进程概念

目录 冯诺依曼体系结构&#xff08;了解&#xff09; 周边知识 操作系统 如何管理 解释打印 ★库函数 ★系统调用 进程 概念 PCB 结构示意图 系统调用 监控脚本 gitpid / gitppid 解释样例 chdir /proc 解释样例 运行起来后删除磁盘中小体积的可执行程序 …

RHCSA第二次作业

4、将整个 /etc 目录下的文件全部打包并用 gzip 压缩成/back/etcback.tar.gz 5、使当前用户永久生效的命令别名&#xff1a;写一个命令命为hello,实现的功能为每输入一次hello命令&#xff0c;就有hello&#xff0c;everyone写入文件/file.txt中。 6、创建mygroup组群&#xff…

IDEA关联Tomcat——最新版本IDEA 2024

1.链接Tomcat到IDEA上 添加Tomcat到IDEA上有两种方式&#xff1a; 第一种&#xff1a; &#xff08;1&#xff09;首先&#xff0c;来到欢迎界面&#xff0c;找到左侧的Customize选项 &#xff08;2&#xff09;然后找到Build、Execution、Deployment选项 &#xff08;3&am…

ruoyi域名跳转缓存冲突问题(解决办法修改:session名修改session的JSESSIONID名称)

【版权所有&#xff0c;文章允许转载&#xff0c;但须以链接方式注明源地址&#xff0c;否则追究法律责任】【创作不易&#xff0c;点个赞就是对我最大的支持】 前言 仅作为学习笔记&#xff0c;供大家参考 总结的不错的话&#xff0c;记得点赞收藏关注哦&#xff01; 目录 前…