数据结构《LinkeList 双向链表》

news2025/4/17 17:33:49

LinkeList

LinkeList 的低层是由双向链表结构组成的,所有元素都是存放到单独的节点当中,通过地址引用将节点串联起来
因此在任意位置插入或删除元素时,都不在需要移动元素,效率较高

下面是双向链表的结构图:

在这里插入图片描述
在集合框架中,LinkedList也实现了List接口,具体如下:
在这里插入图片描述

  1. LinkedList实现了List接口
  2. LinkedList的底层使用了双向链表
  3. LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问
  4. LinkedList的任意位置插入和删除元素时效率比较高,时间复杂度为O(1)

LinkeList 的模拟实现

之前模拟过单向链表,所以前三个方法就不画图了,很简单

public class MyLinkedList {
    static class ListNode{
       public int val;  // 数值
       public ListNode prev;
       public ListNode next;
       // 初始化数值
        public ListNode(int val) {
            this.val = val;
        }
    }
    public ListNode head; //标记头节点
    public ListNode tail; //标记尾节点

    //打印双向链表的节点
    public void display(){
        ListNode cur = head;
        while (cur != null){
            System.out.println(cur.val+" ");
        }
        System.out.println();
    }

    //获取双向链表的长度
    public int size() {
        ListNode cur = this.head;
        int count = 0;
        while(cur != null){
            count++;
            cur = cur.next;
        }
        return count;
    }

    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key) {
        ListNode cur = this.head;
        while(cur != null){
            if(cur.val == key){
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

}

头插法

在双向链表的第一个节点的前面里添加一个新的节点

图解:
在这里插入图片描述
源代码:

public void addFirst(int data) {
        ListNode node = new ListNode(data);
        // 如果链表里没有节点的情况
        if(head == null){
            this.head = node;//第一个节点
            this.tail = node;// 最后一个节点
        }else {
            node.next = this.head;
            this.head.prev = node;
            this.head = node;
        }

    }

尾插法

在双向链表的最后一个节点的后里添加一个新的节点

图解
在这里插入图片描述

源代码:

 public void addLast(int data) {
        ListNode node = new ListNode(data);
        // 如果链表里没有节点的情况
        if(this.head == null){
            this.head = node;//第一个节点
            this.tail = node;// 最后一个节点
        }else {
            this.tail.next = node;
            node.prev =  this.tail;
            tail = node;
        }
    }

在任意位置插入节点

在双链表的任意位置插入一个新的节点
图解

在这里插入图片描述

源代码:

  //任意位置插入节点
    public void addIndex(int index,int data) {
        // 1. 判断位置的合法性
        if(index < 0 || index > size()){
            System.out.println("index位置不合法");
            throw new IndxWrongFulException("index位置不合法");
        }
        // 2. 特殊情况:头节点和尾节点
        if(index ==  0){
            addFirst(data);
            return;
        }
        if(index == size()){
            addLast(data);
            return;
        }
        // 3. 找到要插入节点的位置
        ListNode cur = findIndexListNOde(index);
        // 4. 修改指向
        ListNode node = new ListNode(data); // 创建插入节点
        node.next = cur;
        cur.prev.next = node;
        node.prev = cur.prev;
        cur.prev = node;
    }
    // 查找插入节点的位置
    public ListNode findIndexListNOde(int index){
        ListNode cur = this.head;
        while (index != 0){
            cur = cur.next;
            index--;
        }
        return cur;
    }

删除第一次出现的节点

给一个数值,删除第一次的位置的节点
在这里插入图片描述
源代码:


    //删除第一次出现关键字为key的节点
    public void remove(int key) {
        ListNode cur = this.head;
        // 历遍链表
        while(cur != null){
            // 1. 找到要删除的节点
            if(cur.val == key){
                // 如果要删除的是头节点的情况下
                if(cur == head){
                    head = head.next; // 把 head 向前挪一步
                    if(head != null){
                        head.prev = null; // 把 head.prev 置为 null
                    }else {
                        // 如果只有一个节点的情况下
                        this.tail = null;
                    }
                }else{
                    // 把 cur.next 赋值给后面的节点的next
                    cur.prev.next = cur.next;
                    // 如果是删除中间节点
                    if(cur.next != null){
                        // 把 cur.prev 赋值给前一个节点的prev
                        cur.next.prev = cur.prev;
                    }else {
                        // 如果删除的是尾节点  tail向后走一步
                        this.tail = cur.prev;
                    }
                }
                return;// 删除完节点后返回
            }
            cur = cur.next;
        }
   }

清空链表

删除链表的所有元素
不能直接把 head 和 tail 置空,因为中间的节点还在引用
只能一个一个节点置空

public void clear() {
        ListNode cur = head.next;
        while (cur != null){
            ListNode curNode = cur.next;
            cur.next = null;
            cur.prev = null;
            cur =  curNode;
        }
        head = null;
        tail = null;
    }

LinkeList 的使用

构造方法

方法解释
LinkedList()无参构造 , 创建一个新的空linkedList
LinkedList(Collection<? extends E> c)一个其他集合容器中元素构造List

示例:

 public static void main(String[] args) {
        //无参构造
        LinkedList<Integer> list = new LinkedList<>();
        list.add(1); // 默认尾插法
        list.add(2);
        list.add(3);
        System.out.println(list);
        //传参数构造  只要实现list接口都可以传
        ArrayList<Integer> arr = new ArrayList<>();
        arr.add(11);
        arr.add(12);
        arr.add(13);
        LinkedList<Integer> list2 = new LinkedList<>(arr);
        System.out.println(list2);
    }

结果:

在这里插入图片描述

LinkeList 常用方法

方法解释
boolean add(E e)尾插法
void add (int index, E element)将 e 插入到 index 位置
boolean addAll(Collection<? extends E> c) 尾插 c 中的元素
boolean addAll(int index, Collection<? extends E> c) 将 c 中的元素添加到指定位置
E remove(int index)删除 index 位置元素
boolean remove(Object o)删除遇到的第一个 o
E remove()删除第一个元素
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) 截取部分 lis

方法使用示例:

public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<>();
        list.add(1);   list.add(2);
        list.add(3);   list.add(4);
        list.add(5);   list.add(6);
        System.out.println(list);

        // 在任意位置插入元素
        System.out.println("=======在任意位置插入元素========");
        list.add(0,0);
        System.out.println(list);
        // 删除元素
        System.out.println("=======删除元素========");
        list.remove();// 删除第一个元素
        list.removeLast();// 删除最后一个元素
        list.remove(2);// 删除第二个下标的元素
        System.out.println(list);

        // 判断是否存在
        System.out.println("=======判断元素是否存在========");
        System.out.println(list.contains(1));

        //找元素的位置
        System.out.println("=======找 index 元素的位置========");
        System.out.println(list.indexOf(1)); // 从前往后找第一个1的位置
        System.out.println(list.lastIndexOf(1)); //  从后往前找第一个1的位置

        // 获取 index 下标的元素
        System.out.println("=======找 index 下标的元素========");
        int elem = list.get(0); // get(index): 获取指定位置元素
        System.out.println(elem);

        // 将index位置的元素设置为elem
        System.out.println("=======将index位置的元素设置为elem========");
        list.set(0, 100); // set(index, elem): 将index位置的元素设置为elem
        System.out.println(list);
        // subList(from, to)  复制链表中 frow 到 to 之间的元素存放到新的变量中
        System.out.println("=======复制链表from到to的元素到新的变量中========");
        List<Integer> cur = list.subList(0,3);
        System.out.println(cur);//打印复制的元素

        // 清空链表的所有元素
        System.out.println("=======清空链表的所有元素========");
        list.clear(); // 将list中元素清空
    }

LinkedList的遍历

LinkedList的遍历可以通过二种方式:for循环 或者 foreach遍历

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);
        //通过for循环遍历
        int size = list.size();
        for (int i = 0; i < size; i++) {
            System.out.print(list.get(i)+" ");
        }
        System.out.println();
        //通过foreach遍历
        for (int e:list) {
            System.out.print(e + " ");
        }
        System.out.println();
    }
  • 通过迭代器遍历
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);

        // 使用迭代器遍历---正向遍历
        ListIterator<Integer> it = list.listIterator();
        while(it.hasNext()){
            System.out.print(it.next()+ " ");
        }
        System.out.println();
        // 此时迭代器指向的是链表最后一个元素后面的位置
        // 此时不能再去正向遍历, 因为后面已经没有元素了, 只能去反向遍历
        // 使用反向迭代器---反向遍历
        ListIterator<Integer> rit = list.listIterator(list.size());
        while (rit.hasPrevious()){
            System.out.print(rit.previous() +" ");
        }
        System.out.println();
    }

ArrayList和LinkedList的区别

不同点ArrayListLinkedList
存储空间上物理上一定连续逻辑上连续 , 但物理上不一定连续
随机访问支持 : 时O(1)不支持 : 时O(N)
插入/删除需要搬移元素 , 效率低 , 时O(N)只需要修改引用的指向 , 时O(1)
插入空间不够时需要扩容没有容量的概念
应用场景有限元素个数高效存储+频繁访问任意位置插入和删除频繁

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

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

相关文章

【从零开始游戏开发】静态资源优化 | 全面总结 |建议收藏

你知道的越多&#xff0c;你不知道的越多 &#x1f1e8;&#x1f1f3;&#x1f1e8;&#x1f1f3;&#x1f1e8;&#x1f1f3; 点赞再看&#xff0c;养成习惯&#xff0c;别忘了一键三连哦 &#x1f44d;&#x1f44d;&#x1f44d; 文章持续更新中 &#x1f4dd;&#x1f4dd;…

C++智能指针

文章目录一、智能指针的目的和基本原理二、不带引用计数的智能指针2.1 auto_ptr2.2 scoped_ptr2.3 unique_ptr三、带引用计数的智能指针3.1 shared_ptr3.2 weak_ptr一、智能指针的目的和基本原理 一般new出来的对象会用普通指针引用&#xff0c;此时申请的堆上的资源需要我们手…

乐趣国学—品读《弟子规》中的“泛爱众”之道(上篇)

前言 “泛爱众”就是以广泛的爱心对待社会大众。人类生活是以爱心为纽带&#xff0c;没有爱心&#xff0c;人类生活就太痛苦不堪了。这个爱心从哪里来的&#xff1f;这个爱心就是孝心。孝道&#xff0c;正是培养爱心的第一步&#xff0c;一个连父母都不爱的人决不会真心爱他人&…

19.Feign 的工程化实例:eureka,ribbon,feign,hystrix(springcloud)

项目模型 项目结构 本实例创建model都是通过maven手动创建&#xff0c;依赖进行手动导入&#xff0c;好处是比使用springboot模板创建更加灵活&#xff0c;更方便的进行父子模块的管理。 1.创建父项目feign-project 2.对父项目feign-project的pom.xml&#xff0c;进行手动导入依…

Linux基础内容(10)—— 进程概念

目录 1.冯诺依曼体系结构 ​编辑1.冯诺依曼体系特点 2.cpu运算原理 3.数据传输 2.操作系统 1.操作系统管理的真相 2.操作系统与硬件的交互方式 3.操作系统与用户的交互方式 1.系统调用接口 2.用户对系统调用的使用 3.进程 1.进程的概念 2.Linux中的进程 3.与进程…

基于DJYOS的SPI驱动编写指导手册

1.贡献者列表 深圳市秦简计算机系统有限公司DJYOS驱动开发团队。 2.概述 DJYOS的DjyBus总线模型为IIC、SPI之类的器件提供统一的访问接口&#xff0c;SPIBUS模块是DjyBus模块的一个子模块&#xff0c;为SPI器件提供统一的编程接口&#xff0c;实现通信协议层与器件层的分离。…

Python 考试练习题 2

一、选择题 1、下列是 python 合法标识符的是&#xff08; B&#xff09;。 A. 2variable B. variable2 C. $anothervar D. if 2、在 python 中字符串的表示方式是&#xff08;D &#xff09;。 A.采用单引号包裹 B.采用双引号包裹 C.采用三重单引号包裹 D.ABC 都是 3、设有…

【浅学Linux】动态库与静态库的封装与使用

朋友们好&#xff0c;这里简单介绍一下LINUX学习中关于动态库与静态库的理解&#xff0c;以及站在封装和使用的角度去介绍是如何封装的&#xff1f;如何使用的&#xff1f; 文章目录一&#xff1a;动态库与静态库的理解二&#xff1a;静态库2.1&#xff1a;静态库的使用2.2&…

Sprite Editor

1、SpriteEditor SpriteEditor是精灵图片编辑器 它主要用于编辑2D游戏开发中使用的Sprite精灵图片 它可以用于编辑 图集中提取元素&#xff0c;设置精灵边框&#xff0c;设置九宫格&#xff0c;设置轴心&#xff08;中心&#xff09;点等等功能 2、Single图片编辑 Sprite Ed…

Docker原生网络、自定义网络、Docker容器通信、跨主机容器网络

Docker原生网络、自定义网络、Docker容器通信、跨主机容器网络Docker原生网络bridgeHostnoneDocker自定义网络自定义bridgeoverlaymacviandocker network所有基本命令Docker容器通信双冗余机制跨主机容器网络一些遗留错误解决错误1错误2错误3错误4Docker原生网络 docker安装时…

如何搭建node_exporter

如何搭建node_exporter 1.观看条件 1.假设你已经看过上一篇文章 《如何搭建普罗米修斯 Prometheus》 2.假设你已经会搭建普罗米修斯&#xff08;promethus&#xff09; 3.上面两个假设&#xff0c;只要满足一个。那你看这篇文章就没什么压力了 2.node_exporter是啥 node_…

UI自动化测试之selenium工具(浏览器窗口的切换)

目录 前言 方法 实例 ①示例1 ②示例2 附加知识 结语 前言 1、在浏览网页的时候&#xff0c;有时点击一个链接或者按钮&#xff0c;会弹出一个新的窗口。这类窗口也被称之为句柄&#xff08;一个浏览器窗口的唯一标识符&#xff0c;通过句柄实现不同浏览器窗口之间的切…

C++Qt开发——文件操作

简介 QT中的IO操作通过统一的接口简化了文件与外部设备的操作方式&#xff0c;QT中文件被当作一种特殊的外部设备&#xff0c;文件操作与外部设备操作相同。IO操作的本质是连续存储空间的数据读写。 1. IO设备的类型 顺序存取设备&#xff1a;只能从头开始顺序读写数据&#…

python游戏库pygame经典教程

目录 一.Pygame程序基本搭建过程 1.初始化化程序 2.创建Surface对象 3.事件监听 4.游戏循环 二.Pygame Display显示模块详解 1.将Surface对象粘贴至主窗口上 2.设置窗口主窗口 3.填充主窗口背景&#xff0c;参数值RGB 4.设置窗口标题 5.更新屏幕内容 6.pygame.display其他方…

C# VS2022 EF6 + Mysql8.0.31 CodeFirsts使用配置

文章目录环境安装Mysql8.0.31下载Mysql连接器Net版本安装VS2022创建工程添加Nuget包修改配置文件如下准备工作&#xff0c;创建一个Dbcontext类&#xff0c;代码如下打开程序包控制台输入命令第一步第二步第三步第四步简要介绍一下如何添加一个表思路添加User实体类修改dbconte…

NodeMcu arduino ESP8266 搭建mqtt服务(然也物联)以及使用

NodeMcu arduino ESP8266 搭建mqtt服务以及使用 本文章学习借鉴于太极创客团队&#xff0c;以表感谢。官网http://www.taichi-maker.com/ 操作步骤&#xff1a;我们注册然也物联平台&#xff0c;申请社区版本&#xff0c;进行测试。 文章目录NodeMcu arduino ESP8266 搭建mqtt…

【Python基础篇021】黏包现象丨udp的socket服务

&#x1f320;前言 基于udp的socket服务有什么特点&#xff1f;黏包现象是什么&#xff1f;又是如何产生的&#xff1f;udp和tcp哪种会有黏包现象&#xff1f;看完这篇文章相信你会有所收获。 目录 &#x1f320;一、基于udp的socket服务 &#x1f320;二、TCP中的黏包现象 …

保边滤波之引导滤波与领域转换滤波

&#xff08;1&#xff09;引导滤波 局部窗口内输出图像O和引导图像G成线性关系OiakGibk, ∀i∈Ωk 假设输出图像O和输入图像I之间的关系为OiIi−ni&#xff0c;噪声最小即最小化ni&#xff0c;即 每个像素点i包含于多个窗口Ωk&#xff0c;每个窗口都会得到一个a、b值&#…

RK3399平台开发系列讲解(I/O篇)Linux最大文件数的限制机制

平台内核版本安卓版本RK3399Linux4.4Android7.1🚀返回专栏总目录 文章目录 一、Linux最大文件数的限制机制1.1、申请fd过程分析1.2、申请file内核对象过程分析沉淀、分享、成长,让自己和他人都能有所收获!😄 📢在Linux上能打开多少个文件,有两种限制: 第一种:进程级…

十、组件(8)

本章概要 递归组件异步更新队列Teleport 10.11.2 递归组件 组件可以在自己的模板中递归调用自身&#xff0c;但这需要使用 name 选项为组件指定一个内部调用的名称。 当调用 Vue.createApp({}).component({})全局注册组件时&#xff0c;这个全局的 ID 会自动设置为该组件的n…