【数据结构】线性表 ⑥ ( 双循环链表 | 双循环链表插入操作 | 双循环链表删除操作 | LinkedList 双循环链表源码分析 )

news2024/12/24 0:18:13

文章目录

  • 一、双循环链表插入操作处理
  • 二、双循环链表删除操作处理
  • 三、LinkedList 双循环链表源码分析
    • 1、链表节点
    • 2、LinkedList 链表中收尾元素指针
    • 3、链表插入操作
    • 4、链表向指定位置插入操作
    • 5、获取指定索引的元素
    • 6、删除指定索引的元素





一、双循环链表插入操作处理



双循环链表 中 , 需要对 插入 / 删除 / 遍历 操作 进行特殊处理 , 因为需要调节 前驱指针 和 后继指针 两个指针 ;

如 : 双循环链表 中 , 如果要插入元素 , 将 c 节点 插入到 a 节点 和 b 节点 之间 ,

当前的状态是 a 的后继指针 指向 b , b 的前驱指针指向 a ;

如果要实现插入 c 元素 , 则需要

  • 将 a 的 后继指针 指向 c ,
  • 将 c 的 前驱指针 指向 a ,
  • 将 c 的 后继指针 指向 b ,
  • 将 b 的 前驱指针 指向 c ;

插入节点操作 需要执行四个步骤 :

  • ① 将 c 的 前驱指针 指向 a
  • ② 将 a 的 后继指针 指向 c
  • ③ 将 c 的 后继指针 指向 b
  • ④ 将 b 的 前驱指针 指向 c
    在这里插入图片描述




二、双循环链表删除操作处理



下面的链表插入成功 , 顺序为 a , c , b ,

在这里插入图片描述

如果要删除双循环链表中的 c 元素 , 只需要将 a 元素的 后继指针 指向 b , 将 b 元素的 前驱指针 指向 a 即可 ;

c 元素没有指针指向后 , 会自动被内存回收 ;





三、LinkedList 双循环链表源码分析



LinkedList 源码地址 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java


1、链表节点


LinkedList 链表是一个 双循环链表 , 下面的 Node 类 , 就是双循环链表的 节点 ;

    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#1021


2、LinkedList 链表中收尾元素指针


在 LinkedList 双循环链表中 , 维护了 首元素节点指针 transient Node<E> first , 尾元素节点指针 transient Node<E> last , 分别指向 首尾元素 ;

    transient int size = 0;

    /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;

3、链表插入操作


LinkedList 双循环链表 调用 add 方法 添加元素 , 在其中调用了 linkLast 函数 , 将元素插入到了队尾 ;

    /**
     * 将指定的元素追加到此列表的末尾。
     *
     * <p>这个方法等价于 {@link #addLast}.
     *
     * @param e 元素添加到此列表
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }

源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#354


在 linkLast 函数中 , 创建了新的节点 , 将数据设置到了新节点中 , 最后将新节点设置为 尾部节点 ;


注意 , 设置新的尾部节点时 ,

  • 首先 , 保存原来的尾部节点指针 ( 现在不保存 , 之后访问不到了 ) ;
  • 然后 , 将新的节点设置为 尾部节点 ;
  • 最后 , 将原来的 尾部节点 的后继指针 指向新插入的节点 ;
    /**
     * 链接作为最后一个元素。
     */
    void linkLast(E e) {
    	// 先保存尾结点的指针
        final Node<E> l = last;
        // 创建一个新节点 , 将数据插入到新节点中
        final Node<E> newNode = new Node<>(l, e, null);
        // 将新节点赋值给 尾部元素节点指针 
        last = newNode;
        if (l == null)
        	// 链表是空的 , 该节点是插入的第一个节点
            first = newNode;
        else
        	// 链表不为空 , 该节点是插入的除第一个节点之外的后续节点
            l.next = newNode;
        size++;
        modCount++;
    }

源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#147


4、链表向指定位置插入操作


调用 LinkedList 的 public void add(int index, E element) 函数 , 可以向指定索引添加元素 ,

如果添加的非末尾元素 , 则调用 linkBefore 函数 向 链表 中插入数据 ;

    /**
     * 将指定元素插入此列表中的指定位置。
     * 将当前在该位置的元素(如果有的话)
     * 和任何后续元素向右移动(在它们的索引上加1)。
     *
     * @param index 要插入指定元素的索引
     * @param element 要插入的元素
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
    	// 检查索引合法性
        checkPositionIndex(index);

        if (index == size)
        	// 如果是添加到末尾
            linkLast(element);
        else
        	// 如果是添加到非末尾的元素
            linkBefore(element, node(index));
    }

源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#532


void linkBefore(E e, Node<E> succ) 函数 , 是将 E 数据对应的节点插入到 Node<E> succ 数据之前 ;

    /**
     * 在非空节点 succ 前插入元素 e
     */
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        // 获取 succ 节点 前驱节点
        final Node<E> pred = succ.prev;
        // 新建要插入的节点 
        final Node<E> newNode = new Node<>(pred, e, succ);
        // 前驱节点 的 后继指针 指向 新节点
        succ.prev = newNode;
        if (pred == null)
        	// 如果插入的 是 首元素节点
            first = newNode;
        else
        	// 后继节点 的 前驱指针 指向新节点
            pred.next = newNode;
        size++;
        modCount++;
    }

源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#163


5、获取指定索引的元素


LinkedList 中 , 调用 public E get(int index) 函数 , 获取指定索引的元素 ;

checkElementIndex 函数的作用是 检查该索引是否合法 ;

node 函数就是获取 双循环链表 元素的方法 ;

    /**
     * 返回列表中指定位置的元素。
     *
     * @param index 要返回的元素的索引
     * @return 在此列表中指定位置的元素
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#500


Node<E> node(int index) 函数的核心操作 , 就是执行 index - 1 次 循环 , 找到对应的节点并返回 ;

在执行前 判定 index 靠近 首元素 还是 尾部元素 , 如果 index < (size >> 1) 可以判定为 index 处于前半部分 ;

  • 如果 index 靠近 首部元素 , 则正向遍历 ;
  • 如果 index 靠近 尾部元素 , 则逆向遍历 ;
    /**
     * 返回指定元素索引处的(非空)节点。
     */
    Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node<E> x = first;
            // 从前往后遍历
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            // 从后往前遍历
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#596


6、删除指定索引的元素


LinkedList 双循环链表 中 , 调用 public E remove(int index) 函数 , 删除指定索引的元素 ;

删除的核心操作 , 就是 unlink 函数 , 将指定节点从 双循环链表 中脱离 ;

    /**
     * 移除此列表中指定位置的元素。
     * 将所有后续元素向左移动(从它们的索引中减去1)。
     * 返回从列表中删除的元素。
     *
     * @param index 要删除的索引
     * @return the 元素先前在指定位置
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#551


unlink 函数中 , 先获取要删除节点的 前驱节点 和 后继节点 , 然后 执行下面两个操作 :

  • 前驱节点 的 后继指针 指向 后继节点 ;
  • 后继节点 的 前驱指针 指向 前驱节点 ;
    /**
     * Unlinks non-null node x.
     */
    E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        // 获取节点的 前驱节点
        final Node<E> next = x.next;
        // 获取节点的 后继节点 
        final Node<E> prev = x.prev;

        if (prev == null) {
            first = next;
        } else {
        	// 前驱节点 的 后继指针 指向 后继节点
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
        	// 后继节点 的 前驱指针 指向 前驱节点 
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#220

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

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

相关文章

【JVM】6. 堆

文章目录 6.1. 堆&#xff08;Heap&#xff09;的核心概述6.1.1. 堆内存细分6.1.2. 堆空间内部结构&#xff08;JDK7&#xff09;6.1.3. 堆空间内部结构&#xff08;JDK8&#xff09; 6.2. 设置堆内存大小与OOM6.2.1. 堆空间大小的设置6.2.2. OutOfMemory举例 6.3. 年轻代与老年…

[CTF/网络安全] 攻防世界 backup 解题详析

[CTF/网络安全] 攻防世界 backup 解题详析 PHP备份文件名备份文件漏洞成因备份文件名常用后缀姿势总结 题目描述&#xff1a;X老师忘记删除备份文件&#xff0c;他派小宁同学去把备份文件找出来,一起来帮小宁同学吧&#xff01; PHP备份文件名 PHP 脚本文件的备份文件名&#…

【瑞萨RA_FSP】外部中断

文章目录 一、外部引脚中断二、中断过程三、按键外部中断 一、外部引脚中断 1. ICU框图 根据ICU的功能框图可以知道&#xff0c;首先需要配置IRQCR寄存器(IRQ Control Register&#xff0c;IRQ英文全称&#xff1a;Interrupt ReQuest&#xff0c;中文名&#xff1a;中断请求&a…

C++入门篇---(命名空间、缺省参数、以及输入、输出)

前言 c 我来了,恭喜牛牛解锁新世界.开启c的学习之旅. &#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏: &#x1f354;&#x1f35f;&#x1f32f;C语言进阶 &#x1f511;个人信条: &#x1f335;知行合一 &#x1f349;本篇简介:>:讲解C…

30年后,茶产业规模是现在的10倍

做个预言&#xff1a;30年后&#xff0c;茶产业是现在的10倍 【5.21是世界茶日】 杭州中国茶博会&#xff0c;我来啦 人工智能越让生产效率越来越高 常用物质将会唾手可得 人闲着&#xff0c;无法体会活着的意义&#xff0c;才是挑战 田园诗茶生活方式会有一席之地 趣讲大白话&…

【EMC专题】案例:读一读TI的按接口选择ESD器件指南

在TI的官网上看到一份ESD by Interface Selection Guide,也就是按接口选择ESD器件指南。因此想读一读看看一起学习一下。 首先看一下文档,是比较简明的。可以看到不同的接口推荐了一些不同的保护器件。因为应用环境不一样,所有有不同的器件封装(如单体、集成TVS等),这样在…

Spyder可在线使用!?

不同安装&#xff0c;如果想使用spyder进行编程&#xff0c;可以用其在线版&#xff0c;和本地版功能一样&#xff0c;就是有点慢。 另外需要用chrome浏览器&#xff0c;用火狐没法正常访问。 Spyder可以在线使用&#xff0c;所以在没有安装python环境的电脑上&#xff0c;想…

Linux常用命令——hostname命令

在线Linux命令查询工具 hostname 显示和设置系统的主机名 补充说明 hostname命令用于显示和设置系统的主机名称。环境变量HOSTNAME也保存了当前的主机名。在使用hostname命令设置主机名后&#xff0c;系统并不会永久保存新的主机名&#xff0c;重新启动机器之后还是原来的主…

字符串匹配--BF算法和KMP算法

0.前言 字符串函数strstr相信大家都不陌生–就是在一个字符串&#xff08;主串&#xff09;中找查找另一个字符串&#xff08;子串&#xff09;&#xff0c;并返回子串在主串中的位置。那么这个函数是怎么实现的呢&#xff1f;这就涉及字符串匹配的问题&#xff0c;本章就让我们…

Node.js 事件循环和事件派发器

目录 1、process.nextTick() 介绍 2、setTimeout() 3、零延迟 4、setInterval() 5、递归setTimeout 6、setImmediate() 7、Node.js 事件派发器 1、process.nextTick() 介绍 Node.js中 process.nextTick函数以一种特殊的方式与事件循环交互。 当你试图理解Node.js事件循…

Redis数据结构——QuickList、SkipList、RedisObjective

承接上文&#xff0c;本文主要介绍QuickList、SkipList、RedisObjective 四、 Redis数据结构-QuickList 问题1&#xff1a;ZipList虽然节省内存&#xff0c;但申请内存必须是连续空间&#xff0c;如果内存占用较多&#xff0c;申请内存效率很低。怎么办&#xff1f; ​ 答&a…

计算机操作系统(慕课版)第二章课后题答案

一、简答题 (1)什么是前趋图&#xff1f;试画出下面四条语句的前趋图. S1&#xff1a;axy&#xff1b; S2&#xff1a;bz1&#xff1b; S3&#xff1a;ca-b&#xff1b; S4&#xff1a;wc1&#xff1b; 答&#xff1a;前趋图(Precedence Graph)是一个有向无循环图&#xff0c;…

chatgpt赋能Python-pythondataframe取出一列

用 Python Dataframe 取出一列 数据分析中&#xff0c;用到的数据往往是有多列多行的。而在实际的分析过程中&#xff0c;我们需要针对其中的某一列进行处理。这个时候&#xff0c;Python中的Dataframe就成了我们的利器。 在这篇文章中&#xff0c;我们将教你如何使用Python …

chatgpt赋能Python-pythongit

PythonGit&#xff1a;使Git操作更加高效 Git作为目前最流行的版本控制工具之一&#xff0c;已经被广泛应用于软件开发、Web开发等领域。PythonGit则是一个基于Python编写的Git客户端库&#xff0c;可以让开发者们更加高效地进行Git操作&#xff0c;提高开发效率。 PythonGit…

Qt Quick系列(2)—核心元素类型(1)

作者&#xff1a;CCAccept 专栏&#xff1a;Qt Quick 文章目录 前言ItemRectangleTextImageMouseArea 总结 前言 Qt Quick的元素分为 1、视觉元素&#xff08;如Rectangle&#xff09;具有几何属性 2、非视觉元素&#xff08;如Timer&#xff09;提供一般功能&#xff0c;用…

learn C++ NO.5 ——类和对象(3)

日期类的实现 在前面类和对象的学习中&#xff0c;由于知识多比较多和碎&#xff0c;需要一个能够将之前所学知识融会贯通的东西。下面就通过实现日期类来对类和对象已经所学的知识进行巩固。 日期类的基本功能&#xff08;.h文件&#xff09; //Date.h//头文件内容 #includ…

makefile 学习(4): makefile基础

0. 官方文档 GNU Make 官方网站: https://www.gnu.org/software/makeGNU Make 官方文档下载地址: https://www.gnu.org/software/make/manual/Makefile Tutorial:https://makefiletutorial.com/ 1.基本要求 1.1 基本格式 targets : prerequisties [tab键] command target : …

一、MongoDB简介

文章目录 一、MongoDB简介1、NoSQL简介2、什么是MongoDB ?3、MongoDB 特点4、安装mongodb5、MongoDB 概念解析5.1 数据库5.2 文档5.3 集合5.4 MongoDB 数据类型 6、适用场景 一、MongoDB简介 1、NoSQL简介 NoSQL(NoSQL Not Only SQL)&#xff0c;意即反SQL运动&#xff0c;…

关于在spyder,jupyter notebook下创建虚拟环境(pytorch,tensorflow)均有效

anaconda下载地址 https://www.anaconda.com/download/ 下载完成后打开anaconda目录下的 anaconda prompt 在命令行中输入下面的命令创建一个叫tf2.0的虚拟环境&#xff08;“tf2.0”是建立的Conda虚拟环境的名字&#xff0c;可以自拟&#xff09; conda create -n tf2.0 p…

chatgpt赋能Python-pythonfor遍历

Python for 遍历&#xff1a;优雅地遍历数据结构 对于任何编程语言来说&#xff0c;遍历是一项基本操作。而在 Python 中&#xff0c;遍历是一项非常简单和优雅的操作。Python 提供了多种遍历数据结构的方法&#xff0c;包括 for 循环、while 循环、迭代器和生成器等。本文将介…