LinkedList和链表(下)

news2024/11/24 16:00:11

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)
插入空间不顾时需要扩容没有容量概念
应用场景元素高效存储+频繁访问任意位置频繁插入和删除

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

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

相关文章

DevOps赋能:优化业务价值流的实战策略与路径(下)

下篇&#xff1a;加速工作项流动与持续改进优化 —— 跨越差距&#xff0c;迈向卓越交付 在上篇中&#xff0c;我们已经深入探讨了看板方法的四大核心实践&#xff0c;它们共同致力于实现“顺畅且高质量地交付价值”的终极目标。然而&#xff0c;理想与现实之间往往存在一定的…

day14:RSYNC同步

一&#xff0c;概述 概述 rsync &#xff08;开源&#xff09;是一个高效的文件同步和传输工具&#xff0c;广泛用于 Linux 和 Unix 系统中。它可以在本地和远程系统之间同步文件和目录&#xff0c;同时支持增量备份&#xff0c;能够只传输更改过的文件部分&#xff0c;以减少…

Leaflet查询矢量瓦片偏移的问题

1、问题现象 使用Leaflet绘制工具查询出来的结果有偏移 2、问题排查 1&#xff09;Leaflet中latLngToContainerPoint和latLngToLayerPoint的区别 2&#xff09;使用Leaflet查询需要使用像素坐标 3&#xff09;经排查发现&#xff0c;container获取的坐标是地图容器坐标&…

JSP水果商城管理系统WEB项目

一、项目简介 > FruitsDay 是一个用于销售水果的WEB商城项目。 > 该项目主要通过Java和JSP实现&#xff0c;旨在帮助用户实现网购水果&#xff0c;并帮助商家管理水果库存。 > 项目采用Maven构建&#xff0c;使用JSP和Servlet实现&#xff0c;比较适合JAVA初学者…

Rust 力扣 - 1. 两数相加

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 我们使用一个全局的备忘录&#xff0c;然后我们遍历数组&#xff0c;如果当前元素在备忘录里面找到了&#xff0c;就返回备忘录里面记录的下标和当前下标记录&#xff0c;没找到就把当前元素匹配的元素和当前元素…

ssm016基于 Java Web 的校园驿站管理系统(论文+源码)_kaic

毕 业 设 计&#xff08;论 文&#xff09; 题目&#xff1a;校园驿站管理系统的设计与实现 摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好…

NCCL安装(Ubuntu等)

目录 一、NCCL的定义二、安装NCCL的原因1、加速多GPU通信2、支持流行的深度学习框架3、提高计算效率4、易于使用和集成5、可扩展性 三、NCCL安装方法1、下载安装包2、更新APT数据库3、使用APT安装libnccl2包&#xff0c;另外&#xff0c;如果需要使用NCCL编译应用程序&#xff…

Spring的IOC技术(配置文件形式)

目录 一、什么是IOC 二、IOC的程序入门 1.创建mavenJava项目&#xff0c;引入依赖 2.编写接口和实现类 3.编写spring核心配置文件(applicationContext.xml) 4.测试类 5.测试结果-------一个对象 三、Spring框架的Bean管理的配置文件 四、实例化Bean对象的三种方式 1.无…

论文阅读(三十二):EGNet: Edge Guidance Network for Salient Object Detection

文章目录 1.Introduction2.Related Works3.Salient Edge Guidance Network3.1Complementary information modeling3.1.1Progressive salient object features extraction3.1.2Non-local salient edge features extraction 3.2One-to-one guidance module 4.Experiments4.1Imple…

深度学习:卷积神经网络中的img2col

im2col 是一种在卷积神经网络&#xff08;CNN&#xff09;中常用的技术&#xff0c;用于将输入图像数据转换为适合卷积操作的矩阵形式。通过这种转换&#xff0c;卷积操作可以被高效地实现为矩阵乘法&#xff0c;从而加速计算。 在传统的卷积操作中&#xff0c;卷积核&#xff…

java:入门基础(1)

练习一&#xff1a;文字版格斗游戏 需求: ​ 格斗游戏&#xff0c;每个游戏角色的姓名&#xff0c;血量&#xff0c;都不相同&#xff0c;在选定人物的时候&#xff08;new对象的时候&#xff09;&#xff0c;这些信息就应该被确定下来。 举例&#xff1a; ​ 程序运行之后…

计算机毕业设计 | springboot+vue电影院会员管理系统 影院后台管理(附源码)

1&#xff0c;项目背景 随着互联网时代的到来&#xff0c;同时计算机网络技术高速发展&#xff0c;网络管理运用也变得越来越广泛。因此&#xff0c;建立一个B/S结构的电影院会员管理系统&#xff1b;电影院会员管理系统的管理工作系统化、规范化&#xff0c;也会提高影院形象…

TypeScript(中)+算法(二)

文章目录 算法排序冒泡排序选择排序 TS类型声明类型推断类型总览js中的数据类型ts中的数据类型原始类型和包装对象原始类型包装对象自动装箱 常用类型与语法anyunknownnevervoidvoid 与 undefined总结 算法 排序 有十种排序算法&#xff0c;分别是&#xff1a;冒泡排序&#…

算法定制LiteAIServer视频智能分析软件的过亮、过暗及抖动检测应用场景

在现代社会中&#xff0c;视频监控系统扮演着举足轻重的角色&#xff0c;其视频质量直接关乎监控系统的可靠性与有效性。算法定制LiteAIServer通过引入抖动检测和过亮过暗检测功能&#xff0c;为视频监控系统的稳定性和用户体验带来了显著提升。 以下是对这两种功能的技术实现、…

【隐私计算篇】全同态加密应用场景案例(隐私云计算中的大模型推理、生物识别等)

1.题外话 最近因为奖项答辩&#xff0c;一直在忙材料准备&#xff0c;过程非常耗费时间和精力&#xff0c;很难有时间来分享。不过这段时间虽然很忙碌&#xff0c;但这期间有很多新的收获&#xff0c;特别是通过与领域内专家的深入交流和评审过程&#xff0c;对密码学和隐私计算…

elasticsearch 8.x 插件安装(三)之拼音插件

elasticsearch 8.x 插件安装&#xff08;三&#xff09;之拼音插件 elasticsearch插件安装合集 elasticsearch插件安装&#xff08;一&#xff09;之ik分词器安装&#xff08;含MySQL更新&#xff09; elasticsearch 8.x插件&#xff08;二&#xff09;之同义词安装如何解决…

mac-ubuntu虚拟机(扩容-共享-vmtools)

一、磁盘扩容 使用GParted工具对Linux磁盘空间进行扩展 https://blog.csdn.net/Time_Waxk/article/details/105675468 经过上面的方式后还不够&#xff0c;需要再进行下面的操作 lvextend 用于扩展逻辑卷的大小&#xff0c;-l 选项允许指定大小。resize2fs 用于调整文件系统的…

2024 年 11 个最佳开源网络爬虫和抓取工具

用于网络爬行的免费软件库、软件包和 SDK?或者它是您需要的网络抓取工具吗? 嘿,我们是 Apify 。您可以在 Apify 平台上构建、部署、共享和监控您的抓取工具和爬虫。 来看看我们吧。 如果您厌倦了专有网络抓取工具的限制和成本,或者厌倦了被单一供应商锁定,开源网络爬虫和…

Centos安装ZooKeeper教程(单机版)

本章教程介绍,如何在Centos7中,安装ZooKeeper 3.9.3版本。 一、什么是ZooKeeper ? Apache ZooKeeper 是一个分布式协调服务,用于大型分布式系统中的管理和协调。它为分布式应用提供了一个高性能的通信框架,简化了开发人员在构建复杂分布式系统的任务。ZooKeeper 能够解决一…

检索引擎Elasticsearch

一.为什么要用Elasticsearch 由于我们在运行我们的项目的时候通常都是将数据存到mysql或者sql serve等数据库中&#xff0c;在进行数据搜索时使用sql 语句 like进行模糊匹配查询&#xff0c;其一&#xff1a;虽然可以查到数据&#xff0c;但是它模糊匹配查询速度较慢&#xff0…