Java集合类学习--LinkedList

如何使用 哑节点(dummy node),高效解决问题 目录 前言: 1.19. 删除链表的倒数第 N 个结点 2.24. 两两交换链表中的节点 3.82. 删除排序链表中的重复元素 II 前言: 在对链表进行操作时,一种常用的技巧是添加一个哑节点(dummy node),它的 next 指针指向链表的头节点。这样一来,我们就不需要对头节点进行特殊的判断了,可省去许多麻烦。 1.19. 删除链表的倒数第 N 个结点 问题描述: 思路: 先求出链表长度,后找到要被删除节点的前一个节点。第二次遍历是是从哑巴节点开始的。 代码: ... 阅读详情

概述

LinkedList 内部是一个双向链表,并且实现了 List 接口和 Deque 接口,因此它也具有 List 的操作以及双端队列和栈的性质。双向链表的结构如下:
在这里插入图片描述
LinkedList 的继承结构如下:
在这里插入图片描述

源码分析

重要部分
1、结点类 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;
    }
}

Node 类则是链表的结点。
2、成员变量

// 元素个数
transient int size = 0;
// 链表首节点
transient Node<E> first;
// 链表尾节点
transient Node<E> last;

构造方法
LinkedList 有两个构造器,如下:

//无参
public LinkedList() {
}
//有参,使用addAll方法
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

PS: 由于链表的容量可以一直增加,因此没有指定容量的构造器。

跟进addAll()方法

public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);
}

public boolean addAll(int index, Collection<? extends E> c) {
    checkPositionIndex(index);
    // 将集合元素转为数组
    Object[] a = c.toArray();
    int numNew = a.length;
    if (numNew == 0)
        return false;
    // 获取当前链表的前驱和后继结点
    Node<E> pred, succ;
    if (index == size) { 
    //当在链表最后插入时,last变为前节点,后继节点为空.
        succ = null;
        pred = last;
    } else { 
    //在当前节点的前面插入整个数组.
        succ = node(index);
        pred = succ.prev;
    }
    // 循环将数组中的元素插入到链表
    for (Object o : a) {
        @SuppressWarnings("unchecked") E e = (E) o;
        Node<E> newNode = new Node<>(pred, e, null);
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        pred = newNode;
    }
    // 若插入到末尾,则数组中的最后一个元素就是尾结点
    if (succ == null) {
        last = pred;
    } else {
        // 若插入到指定位置,将数组中最后一个元素与下一个位置关联起来
        pred.next = succ;
        succ.prev = pred;
    }
    size += numNew;
    modCount++;
    return true;
}

其中 node(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;
    }
}

node(index)方法并非直接从头到尾遍历整个链表,而是先判断下标的位置,若在前一半则从前往后遍历;否则就从后往前遍历。这样能减少遍历结点的个数。

重要方法

1、新增结点方法:add(), addLast(), offerLast(),addFirst(), offerFirst()

public void addFirst(E e) {
    linkFirst(e);
}

public boolean offerFirst(E e) {
        addFirst(e);
        return true;
    }
public boolean offerLast(E e) {
    addLast(e);
    return true;
}

public void addLast(E e) {
    linkLast(e);
}

public boolean add(E e) {
    linkLast(e);
    return true;
}

跟进方法可以发现,其实内部他们都是调用了同一个方法 linkLast(e) 和linkFirst实现的,如下:

	// 从队列尾添加元素
   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++;
    }

// 从队列首添加元素
private void linkFirst(E e) {
    // 首节点
    final Node<E> f = first;
    // 创建新节点,新节点的next是首节点
    final Node<E> newNode = new Node<>(null, e, f);
    // 让新节点作为新的首节点
    first = newNode;
    // 判断是不是第一个添加的元素
    // 如果是就把last也置为新节点
    // 否则把原首节点的prev指针置为新节点
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    // 元素个数加1
    size++;
    // 修改次数加1,说明这是一个支持fail-fast的集合
    modCount++;
}

因为是链表特性,还需要在中间位置添加元素,则是使用add()l另外方法

// 在指定index位置处添加元素
public void add(int index, E element) {
    // 判断是否越界
    checkPositionIndex(index);
    // 如果index是在队列尾节点之后的一个位置
    // 把新节点直接添加到尾节点之后
    // 否则调用linkBefore()方法在中间添加节点
    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}

跟进linkBefore(),可以发现,在调用linkBefore时也是用了获取当前节点位置的node方法。

// 在节点succ之前添加元素
void linkBefore(E e, Node<E> succ) {
    // succ是待添加节点的后继节点
    // 找到待添加节点的前置节点
    final Node<E> pred = succ.prev;
    // 在其前置节点和后继节点之间创建一个新节点
    final Node<E> newNode = new Node<>(pred, e, succ);
    // 修改后继节点的前置指针指向新节点
    succ.prev = newNode;
    // 判断前置节点是否为空
    // 如果为空,说明是第一个添加的元素,修改first指针
    // 否则修改前置节点的next为新节点
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    // 修改元素个数
    size++;
    // 修改次数加1
    modCount++;
}

2、删除结点方法:poll(), pollFirst(), removeFirst(),pollLast(), removeLast()

public E poll() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}

public E pollFirst() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}

public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

public E removeLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
}

public E pollLast() {
    final Node<E> l = last;
    return (l == null) ? null : unlinkLast(l);
}

可以看到,最终实现也都是unlink开头的一些方法:

// 删除首节点
private E unlinkFirst(Node<E> f) {
    // 首节点的元素值
    final E element = f.item;
    // 首节点的next指针
    final Node<E> next = f.next;
    // 添加首节点的内容,协助GC
    f.item = null;
    f.next = null; // help GC
    // 把首节点的next作为新的首节点
    first = next;
    // 如果只有一个元素,删除了,把last也置为空
    // 否则把next的前置指针置为空
    if (next == null)
        last = null;
    else
        next.prev = null;
    // 元素个数减1
    size--;
    // 修改次数加1
    modCount++;
    // 返回删除的元素
    return element;
}
// 删除尾节点
private E unlinkLast(Node<E> l) {
    // 尾节点的元素值
    final E element = l.item;
    // 尾节点的前置指针
    final Node<E> prev = l.prev;
    // 清空尾节点的内容,协助GC
    l.item = null;
    l.prev = null; // help GC
    // 让前置节点成为新的尾节点
    last = prev;
    // 如果只有一个元素,删除了把first置为空
    // 否则把前置节点的next置为空
    if (prev == null)
        first = null;
    else
        prev.next = null;
    // 元素个数减1
    size--;
    // 修改次数加1
    modCount++;
    // 返回删除的元素
    return element;
}
// 删除指定节点x
E unlink(Node<E> x) {
    // x的元素值
    final E element = x.item;
    // x的前置节点
    final Node<E> next = x.next;
    // x的后置节点
    final Node<E> prev = x.prev;
    
    // 如果前置节点为空
    // 说明是首节点,让first指向x的后置节点
    // 否则修改前置节点的next为x的后置节点
    if (prev == null) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }

    // 如果后置节点为空
    // 说明是尾节点,让last指向x的前置节点
    // 否则修改后置节点的prev为x的前置节点
    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }

    // 清空x的元素值,协助GC
    x.item = null;
    // 元素个数减1
    size--;
    // 修改次数加1
    modCount++;
    // 返回删除的元素
    return element;
}

在这里插入图片描述
向单链表插入和删除结点的操作示意图如下(双链表比这里多了前驱结点):
在这里插入图片描述
作为栈使用时,入栈(push)和出栈(pop)操作:

public void push(E e) {
    addFirst(e);
}

public E pop() {
    return removeFirst();
}

添加删除元素都只操作队列首节点即可。

SAP PP未清生产订单关闭物料退料、新工单发料批量处理 ( werks = '1001' lgort = 'A004' lgobe = '组装-成品组装' )( werks = '1002' lgort = 'C006' lgobe = '组装-二车间' )( werks = '1002' lgort = 'C007' lgobe = '组装-二车间' )( werks = '1001' lgort = 'A007' lgobe = '组装-MS' )( werks = '1001' lgort = 'A008' lgobe = '组装-GW' ) 阅读详情

相关推荐

关于示波器测485串口波特率的使用方法总结

之前没有用过示波器,更不知道怎么来测试串口的波特率,结果遇到一客户说我们产品的波特率达不到9600,只有9100,为了验证这一说法,我们只能自己测试一下。说明:产品通过485接电脑,用串口调试助手接收都是没有问题的,用客户的设备接收就有问题。 下面说一下示波器测波特率的方法步骤: 显示高低跳变的情况: 1.先把示波器表笔的地线(夹子)夹485B,表笔夹485A。 2.往串口发送数据:如发送

lianxue1986的专栏 3万+

算法模板之数据结构篇(2)

链表 链表相关的核心点 NULL 异常处理 dummy node(哑巴节点) 快慢指针 插入一个节点到排序链表 从一个链表中移除一个节点 翻转链表 合并两个链表 找到链表的中间节点 哑巴节点 ​ 哑节点(dummy node)是初始值为NULL的节点,创建在使用到链表的函数中,可以起到避免处理头节点为空的边界问题的作用,减少代码执行异常的可能性。也就是说,哑节点的使用可以对代码起到简化作用(省略当函数的入口参数为空时的判断)。 // 普通结构体 struct ListNode { int va

felicx的博客 797

案例分析|一篇文章带你看懂养鸡场智慧管理系统

养鸡场智慧管理系统是为了满足现代化养鸡业对精细化管理的需求而设计的一款专业软件。随着科技的发展和农业产业的升级,传统的手工记录和管理方式已无法满足高效、准确和实时的数据跟踪需求。本软件旨在通过集成各种功能模块,帮助养鸡场主实现从鸡苗孵化、饲养管理、饲料控制到销售结算等全链条的智能化操作。该系统的核心目标是提升养鸡场的运营效率和盈利能力,通过自动化数据处理,减少人工误差,节省时间和精力。例如,孵化报表和记录模块可以帮助管理者实时监控孵化过程,及时调整策略;

muyu2980的专栏 930

链表哑节点基础

在 C 语言实现链表时,哑节点是一种很实用的设计技巧。它也常被称作 “头节点”(注意和一般意义上存放有效数据的头节点有所不同)或 “虚拟节点”,是一个额外添加在链表头部的节点,这个节点通常不存放实际要处理的数据(不过也可以根据具体情况赋予其特定含义的数据),主要是为了简化链表操作的代码逻辑,尤其是在对链表头部进行插入、删除等操作时能带来很多便利。// 定义链表节点结构体int val;} ListNode;// 创建带有哑节点的链表在上述代码中,dummy就是创建出来的哑节点,初始时它的。

2401_86810295的博客 2191

在Qt中使用EtherCAT-SOEM的初始化中,状态机卡在PreOp->Save-Op过程中,出现State=0x12, Error=0x001e错误的解决办法

1.在调试EtherCAT软件过程中,一直出现配置从站进不了Save_op状态,通过下函数检查从站状态: //使用的从站编号为1 只有1个从站 ec_slave[1].state = EC_STATE_SAFE_OP; ec_writestate(1); //设置到安全模式 ec_statecheck(1, EC_STATE_SAFE_OP, EC_TIMEOUTSTATE * 4); printf("Slave %d State=0x%2.2x StatusCode=0x%4

zeal_for_rov的博客 6828

Polars简明基础教程一:Polars快速入门

在这个例子中,我们将添加一个列,其中包含每个舱位中乘客的最大年龄。在Python中(而不是特指Polars),当我们将代码包裹在括号中时,我们可以在新行上调用一个新方法——在这种情况下是select。在这里,我们看到一个使用表达式API的简单示例,其中我们在select语句中选择Pclass、Name和Age列(我们将在第3节中深入了解select语句)。表达式是一个函数,它接受一个Series(或DataFrame中的列)作为输入,并返回一个Series(或DataFrame中的列)。

sosogod的博客 3878

深入分析AQS实现原理

https://segmentfault.com/a/1190000017372067

meser88的博客 856

EMC意义下的共模(Common Mode)和差模(Differential Mode)

在电子产品中,信号或电源通过两根导线传输。对比维度差模(DM)共模(CM)电流方向一进一出,相等相反同向、相等回流路径两根导线组成的闭合回路地或外壳EMC影响较小容易辐射干扰,重点治理类型定义有用信号干扰信号抑制方法常规滤波器、电感、电容共模电感、磁环、屏蔽、接地技术等。

weixin_43199439的博客 1244

leetcode 链表

Dummy node 是链表问题中一个重要的技巧,中文翻译叫“哑节点”或者“假人头结点”。 Dummy node 是一个虚拟节点,也可以认为是标杆节点。Dummy node 就是在链表表头 head 前加一个节 点指向 head,即 dummy -> head。Dummy node 的使用多针对单链表没有前向指针的问题,保证链表的 head 不会在删除操作中丢失。除此之外,还有一种用法比较少

lifqjdlut的博客 629

面经-2023-联发科MTK-数字芯片设计

面经-2023-联发科MTK-数字芯片设计

w0shishabi的博客 2943

SAP 公司间采购简介

SAP公司间采购(Intercompany Procurement)是指企业集团内部不同法人实体之间的采购和销售流程。这种流程在跨国公司或有多个独立运营子公司的企业中非常常见。SAP提供了标准的功能来处理公司间采购,确保财务和物流流程的透明性和效率。以下是SAP公司间采购的基本流程及其相关模块的简介。

我小时候很黑的博客 1376

学习-Java集合类之List的LinkedList之特有方法的使用

LinkedList 是 List 集合的实现类,它的底层数据结构是链表,因此它具有查询慢,增删快的特点。

千浔的博客 936

1、信息与物理世界:“It from Bit”还是“Bit from It”?

这篇博文探讨了物理学与信息理论之间的深刻联系,从惠勒提出的“It from Bit”概念出发,讨论了信息是否是物质世界的基础,或者物质是否是信息的来源。文章回顾了2013年举办的征文竞赛以及众多学者对这一问题的不同观点,涵盖了量子理论、热力学、计算机科学、黑洞物理以及时空本质等多个领域。同时,文章还分析了信息理论在哲学层面的含义,并展望了其在未来理论、实验和应用研究中的潜力与方向。

water的专栏 289

深入学习Java集合类源码之LinkedList

一.简介: LinkedList是一种可以在任何位置进行高效地插入和移除操作的有序序列,它是基于 双向链表 实现的。 LinkedList可以被当作堆栈、队列或双端队列进行操作。 LinkedList 是非同步的 二.源码分析: 1.继承关系 public class LinkedList<E> extends AbstractSequentialList<E&gt...

atomatom98的博客 232

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

Java集合类学习之Arraylist、Linkedlist

Vector被ArrayList取代线程安全无迭代器Iterator遍历时使用Enumeration Enumeration en=v.elements(); //获取枚举 while(en.hasMoreElements()){ //判断集合是否有元素 System.out.println(en.nextElement()); //获取集合中的元素 }ArrayList底层是数组实...

takethevow的博客 342

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

java集合类 arraylist_Java集合类之ArrayList与LinkedList源码学习笔记

Java集合类主要分为List,Set,Map三大类。ArrayList和LinkedList是则List接口的俩个实现。他们都可以作为顺序存放元素的容器,并且元素可以重复,可以为null值。当有元素重复时,如果按元素而非索引进行一些操作时,则会对List中的第一个符合条件的元素进行操作,如remove(E o)会删除第一个元素。此外,ArrayList与LinkedList并非线程安全的容器,因...

weixin_33467061的博客 235

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

头歌 学习-Java集合类之List的LinkedList之特有方法的使用 12-5

头歌 学习-Java集合类之List的LinkedList之特有方法的使用 12-5 Java 集合学习

m0_64224735的博客 1426

java集合类库学习记录———LinkedList

1.介绍 LinkedList是List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null)。 扩展了AbstractSequentialList抽象类,实现了Deque和Queue接口。这些操作允许将链接列表用作堆栈、队列和双端队列。 2.AbstractSequentialList 要实现一个列表,程序员只需要扩展此类,并提供 listIterator 和

qwelkjzxc369的博客 354

第1关:学习-Java集合类之List的LinkedList之特有方法的使用

任务描述 相关知识 什么是 LinkedList集合 创建 LinkedList 集合 LinkedList 集合的方法 编程要求 测试说明 任务描述 本关任务:替换 LinkedList 集合中的首尾元素。 相关知识 List 集合主要有 ArrayList 和 LinkedList 两个实现类,本实训将介绍 LinkedList 集合。 什么是 LinkedList集合 LinkedList 是 List 集合的实现类,它的底层数据结构是链表,因此它具有查询慢,增删快的特点

君临๑的博客 4888

8.3 学习-Java集合类之List的LinkedList之特有方法的使用

任务描述 本关任务:替换 LinkedList 集合中的首尾元素。

m0_65745343的博客 349

Day11_8 Java学习之自定义LinkedList集合类

解析:1.LinkedList 类采用链表结构保存对象,这种结构的优点是便于向集合中插入或者删除元素。2.需要频繁向集合中插入和删除元素时,使用 LinkedList 类比 ArrayList 类效果高,但是 LinkedList 类随机访问元素的速度则相对较慢。这里的随机访问是指检索集合中特定索引位置的元素。3.它也是长度可变的集合,且集合也是元素有序可重复也是只能存引用数据类型。解析:1.从代码的角度来看,就是一个Node类,链表是由多个节点串起来的,链表又分单向链表与双向链表。...

weixin_43717536的博客 1106

JAVA学习笔记----集合类之LinkedList

PS:目前仅供博主日常学习笔记而已

unclestars的博客 277

头歌 学习-Java集合类之List的LinkedList之特有方法的使用 12-4

头歌 学习-Java集合类之List的LinkedList之特有方法的使用 12-4 Java LinkedList的常用方法及使用

m0_64224735的博客 762

菜鸟修行之路----java集合类三:List之LinkedList源码学习

菜鸟修行之路----java集合类三:List之LinkedList源码学习 ​ ​ LinkedList 是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较 慢。另外,他还提供了 List 接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用 1.LinkedList概述 LinkList主要具有以下特点: 排列有序,可重复 ...

sumeriancer的博客 378

Java集合类学习-LinkedList, ArrayList, Stack, Queue, Vector

Collection List   在Collection的基础上引入了有序的概念,位置精确;允许相同元素。在列表上迭代通常优于索引遍历。特殊的ListIterator迭代器允许元素插入、替换,双向访问,从列表中的制定位置开始(根据index获取index位置的元素)。未实现get方法。 AbstractList   实现了List中的一些方法,支持随机访问。lastIndexOf通过...

weixin_30702413的博客 107
上一篇: Java集合类学习--ArrayList
下一篇: Java集合类学习--BlockingQueue
CodersCoder
博客等级 码龄10年 90粉丝 258原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值