当前位置: 首页 > news >正文

链表——LinkedList类的概述和实现

LinkedList类

1.1LinkedList类概述

  • LinkedList类底层是基于双向链表结构实现的,不同于ArrayList类和Vector类是基于数组实现的;
  • LinkedList类是非线程安全的;
  • LinkedList类元素允许为null,允许重复元素;
  • LinkedList类插入、删除效率高,查询效率低;
  • LinkedList类基于链表实现的,因此不存在容量不足的问题,不用动态扩容;
  • 双向链表有三个区域,前指针域、数据域、后指针域。
  • LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
  • LinkedList 实现 List 接口,能对它进行队列操作。
  • LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
  • LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
  • LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。
  • LinkedList 是非同步的。

1.2LinkedList类的方法

与ArrayList类相似,LinkedList类常见方法也有add()、get()、remove()、set()、size()等,用法也类似。

  • Object getFirst()        返回链表的第一个元素。
  • Object getLast()        返回链接列表的最后一个元素。
  • boolean contains(Object element)如果元素存在于列表中,则返回true。
  • void clear()                删除列表中的所有元素。 

1.3LinkedList类的特点

  • LinkedList的底层结构为双向链表,将零散的内存单元通过附加的引用关联起来体现出其顺序性,相比数组的连续空间存储,链表对内存的利用率更高;
  • 有序:插入元素的顺序和取出顺序一致;
  • 可重复:可以插入相同的元素(元素值也允许为null);
  • 插入、删除操作效率高,和ArrayList恰恰相反,按索引查找效率较低;
  • 线程不安全:没有线程锁,多个线程同步进行写操作的时候可能导致数据错误;
  • 使用场景:不会随机访问数据,更多的插入删除操作,更少的查询读取操作。 

链表

链表通常由一连串节点组成,每个节点包含任意的实例数据(data fields)和一或两个用来指向上一个/或下一个节点的位置的链接(links

链表Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。

使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。

单向链表

普通链表,也叫单链表或者单向链表(Single-Linked List)。单链表是链表中结构最简单的。

一个单链表的节点(Node)分为两个部分,第一个部分(data)保存或者显示关于节点的信息,另一个部分存储下一个节点的地址。最后一个节点存储地址的部分指向空值。

  • 查找:单向链表只可向一个方向遍历,一般查找一个节点的时候需要从第一个节点开始每次访问下一个节点,一直访问到需要的位置。
  • 插入:而插入一个节点,对于单向链表,我们只提供在链表头插入,只需要将当前插入的节点设置为头节点,next指向原头节点即可。
  • 删除:删除一个节点,我们将该节点的上一个节点的next指向该节点的下一个节点。

查找图:

在表头增加节点:

删除节点:

2.1 单向链表的具体实现

public class SingleLinkedList {private int size;//链表节点的个数private Node head;//头节点public SingleLinkedList() {size = 0;head = null;}//链表的每个节点类private class Node {private Object data;//每个节点的数据private Node next;//每个节点指向下一个节点的连接public Node(Object data) {this.data = data;}}//在链表头添加元素public Object addHead(Object obj) {Node newHead = new Node(obj);if (size == 0) {head = newHead;} else {newHead.next = head;head = newHead;}size++;return obj;}//在链表头删除元素public Object deleteHead() {Object obj = head.data;head = head.next;size--;return obj;}//查找指定元素,找到了返回节点Node,找不到返回nullpublic Node find(Object obj) {Node current = head;int tempSize = size;while (tempSize > 0) {if (obj.equals(current.data)) {return current;} else {current = current.next;}tempSize--;}return null;}//删除指定的元素,删除成功返回truepublic boolean delete(Object value) {if (size == 0) {return false;}Node current = head;Node previous = head;while (current.data != value) {if (current.next == null) {return false;} else {previous = current;current = current.next;}}//如果删除的节点是第一个节点if (current == head) {head = current.next;size--;} else {//删除的节点不是第一个节点previous.next = current.next;size--;}return true;}//判断链表是否为空public boolean isEmpty() {return (size == 0);}//显示节点信息public void display() {if (size > 0) {Node node = head;int tempSize = size;if (tempSize == 1) {//当前链表只有一个节点System.out.println("[" + node.data + "]");return;}while (tempSize > 0) {if (node.equals(head)) {System.out.print("[" + node.data + "->");} else if (node.next == null) {System.out.print(node.data + "]");} else {System.out.print(node.data + "->");}node = node.next;tempSize--;}System.out.println();} else {//如果链表一个节点都没有,直接打印[]System.out.println("[]");}}
}

测试:

public void testSingleLinkedList(){SingleLinkedList singleList = new SingleLinkedList();singleList.addHead("A");singleList.addHead("B");singleList.addHead("C");singleList.addHead("D");//打印当前链表信息singleList.display();//删除CsingleList.delete("C");singleList.display();//查找BSystem.out.println(singleList.find("B").data);
}

打印结果:

[D->C->B->A]
[D->B->A]
B

2.2 用单向链表实现栈

栈的pop()方法和push()方法,对应于链表的在头部删除元素deleteHead()以及在头部增加元素addHead()

public class StackSingleLink {private SingleLinkedList link;public StackSingleLink() {link = new SingleLinkedList();}//添加元素public void push(Object obj) {link.addHead(obj);}//移除栈顶元素public Object pop() {Object obj = link.deleteHead();return obj;}//判断是否为空public boolean isEmpty() {return link.isEmpty();}//打印栈内元素信息public void display() {link.display();}
}

双端链表(单向引用)

对于单向链表,我们如果想在尾部添加一个节点,那么必须从头部一直遍历到尾部,找到尾节点,然后在尾节点后面插入一个节点。这样操作很麻烦,如果我们在设计链表的时候多个对尾节点的引用,那么会简单很多。

链表的节点类保留下一节点的引用(单向引用)。这就是双端链表。

双端链表:保留对头节点、尾节点的引用,可以从尾节点插入,但只能从头节点删除,只能从一个方向遍历,相当于单向链表多了一个对尾节点的引用。

双端链表的特点:双端链表的含有对第一个节点和最后一个节点的引用

双端链表的操作读取(引用)插入删除
头部
尾部x

注意和后面讲的双向链表的区别!!!

3.1 双端链表的具体实现

public class DoublePointLinkedList {private Node head;//头节点private Node tail;//尾节点private int size;//节点的个数private class Node {private Object data;private Node next;public Node(Object data) {this.data = data;}}public DoublePointLinkedList() {size = 0;head = null;tail = null;}//链表头新增节点public void addHead(Object data) {Node node = new Node(data);if (size == 0) {//如果链表为空,那么头节点和尾节点都是该新增节点head = node;tail = node;size++;} else {node.next = head;head = node;size++;}}//链表尾新增节点public void addTail(Object data) {Node node = new Node(data);if (size == 0) {//如果链表为空,那么头节点和尾节点都是该新增节点head = node;tail = node;size++;} else {tail.next = node;tail = node;size++;}}//删除头部节点,成功返回true,失败返回falsepublic boolean deleteHead() {if (size == 0) {//当前链表节点数为0return false;}if (head.next == null) {//当前链表节点数为1head = null;tail = null;} else {head = head.next;}size--;return true;}//判断是否为空public boolean isEmpty() {return (size == 0);}//获得链表的节点个数public int getSize() {return size;}//显示节点信息public void display() {if (size > 0) {Node node = head;int tempSize = size;if (tempSize == 1) {//当前链表只有一个节点System.out.println("[" + node.data + "]");return;}while (tempSize > 0) {if (node.equals(head)) {System.out.print("[" + node.data + "->");} else if (node.next == null) {System.out.print(node.data + "]");} else {System.out.print(node.data + "->");}node = node.next;tempSize--;}System.out.println();} else {//如果链表一个节点都没有,直接打印[]System.out.println("[]");}}
}

3.2 用双端链表实现队列

public class QueueLinkedList {private DoublePointLinkedList dp;public QueueLinkedList() {dp = new DoublePointLinkedList();}public void insert(Object data) {dp.addTail(data);}public void delete() {dp.deleteHead();}public boolean isEmpty() {return dp.isEmpty();}public int getSize() {return dp.getSize();}public void display() {dp.display();}}

双向链表

我们知道单向链表只能从一个方向遍历,那么双向链表(Double-Linked List)它可以从两个方向遍历。

具体代码实现:

public class TwoWayLinkedList {private Node head;//表示链表头private Node tail;//表示链表尾private int size;//表示链表的节点个数private class Node {private Object data;private Node next;private Node prev;public Node(Object data) {this.data = data;}}public TwoWayLinkedList() {size = 0;head = null;tail = null;}//在链表头增加节点public void addHead(Object value) {Node newNode = new Node(value);if (size == 0) {head = newNode;tail = newNode;size++;} else {head.prev = newNode;newNode.next = head;head = newNode;size++;}}//在链表尾增加节点public void addTail(Object value) {Node newNode = new Node(value);if (size == 0) {head = newNode;tail = newNode;size++;} else {newNode.prev = tail;tail.next = newNode;tail = newNode;size++;}}//删除链表头public Node deleteHead() {Node temp = head;if (size != 0) {head = head.next;head.prev = null;size--;}return temp;}//删除链表尾public Node deleteTail() {Node temp = tail;if (size != 0) {tail = tail.prev;tail.next = null;size--;}return temp;}//获得链表的节点个数public int getSize() {return size;}//判断链表是否为空public boolean isEmpty() {return (size == 0);}//显示节点信息public void display() {if (size > 0) {Node node = head;int tempSize = size;if (tempSize == 1) {//当前链表只有一个节点System.out.println("[" + node.data + "]");return;}while (tempSize > 0) {if (node.equals(head)) {System.out.print("[" + node.data + "->");} else if (node.next == null) {System.out.print(node.data + "]");} else {System.out.print(node.data + "->");}node = node.next;tempSize--;}System.out.println();} else {//如果链表一个节点都没有,直接打印[]System.out.println("[]");}}
}

有序链表

前面的链表实现插入数据都是无序的,无序链表最大特点就是在头部或尾部增加新节点。在有些应用中需要链表中的数据有序。

有序链表:递增,递减或者其他满足一定条件的规则,插入数据时,从头结点开始遍历每个节点,在满足规则的地方插入新节点。

在有序链表中,数据是按照关键值有序排列的。一般在大多数需要使用有序数组的场合也可以使用有序链表。有序链表优于有序数组的地方是插入的速度(因为元素不需要移动),另外链表可以扩展到全部有效的使用内存,而数组只能局限于一个固定的大小中。

public class OrderLinkedList {private Node head;private class Node {private int data;private Node next;public Node(int data) {this.data = data;}}public OrderLinkedList() {head = null;}//插入节点,并按照从小打到的顺序排列public void insert(int value) {Node node = new Node(value);Node pre = null;Node current = head;while (current != null && value > current.data) {pre = current;current = current.next;}if (pre == null) {head = node;head.next = current;} else {pre.next = node;node.next = current;}}//删除头节点public void deleteHead() {head = head.next;}public void display() {Node current = head;while (current != null) {System.out.print(current.data + " ");current = current.next;}System.out.println("");}
}

在有序链表中插入和删除某一项最多需要O(N)次比较,平均需要O(N/2)次,因为必须沿着链表上一步一步走才能找到正确的插入位置,然而可以最快速度删除最值,因为只需要删除表头即可,如果一个应用需要频繁的存取最小值,且不需要快速的插入,那么有序链表是一个比较好的选择方案。比如优先级队列可以使用有序链表来实现。

5.1 有序链表和无序数组组合排序

比如有一个无序数组需要排序,解冒泡排序、选择排序、插入排序这三种简单的排序时,需要的时间级别都是O(N^2^)

现在我们讲解了有序链表之后,对于一个无序数组,我们先将数组元素取出,一个一个的插入到有序链表中,然后将他们从有序链表中一个一个删除,重新放入数组,那么数组就会排好序了。

和插入排序一样,如果插入了N个新数据,那么进行大概N^2^/4次比较。但是相对于插入排序,每个元素只进行了两次排序,一次从数组到链表,一次从链表到数组,大概需要2*N次移动,而插入排序则需要N^2^次移动,

效率肯定是比前面讲的简单排序要高,但是缺点就是需要开辟差不多两倍的空间,而且数组和链表必须在内存中同时存在,如果有现成的链表可以用,那么这种方法还是挺好的。

总结

上面我们讲了各种链表,每个链表都包括一个LinkedList对象和许多Node对象,LinkedList对象通常包含头和尾节点的引用,分别指向链表的第一个节点和最后一个节点。

而每个节点对象通常包含数据部分data,以及对上一个节点的引用prev和下一个节点的引用next,只有下一个节点的引用称为单向链表,两个都有的称为双向链表。

next值为null则说明是链表的结尾,如果想找到某个节点,我们必须从第一个节点开始遍历,不断通过next找到下一个节点,直到找到所需要的。

栈和队列都是ADT,可以用数组来实现,也可以用链表实现。

相关文章:

链表——LinkedList类的概述和实现

LinkedList类 1.1LinkedList类概述 LinkedList类底层是基于双向链表结构实现的,不同于ArrayList类和Vector类是基于数组实现的;LinkedList类是非线程安全的;LinkedList类元素允许为null,允许重复元素;LinkedList类插…...

快六一啦,学习CSS3实现一个冰淇淋动画特效

快六一啦,小时候顶多吃个小冰棍,或者是那种小冰袋,现在的小朋友真是好,动不动就能吃到冰淇淋,今天用CSS3实现一个冰淇淋的动画特效吧 目录 实现思路 桶身的实现 冰淇淋身体的实现 五彩颗粒的实现 HTML源码 CSS3源…...

VSCode CMake vcpkg 整合

VSCode 整合 CMake 调试 CMake 工程 // launch.json {"version": "0.2.0","configurations": [{"name": "(gdb) Launch","type": "cppdbg","request": "launch",// Resolved by …...

c++ | win vscode

vscode 适合新手做一些简单的单个的编译和调试 新手适合去配置c 环境,尤其是当涉及复杂一点的编程,如多文件、多线程,在调试的时候会头大,要求会高一点 但怎么说呢? c 编译和调试是最接近实际开发环境的,与…...

算法-快速排序

给你一个整数数组 nums,请你将该数组升序排列。 输入:nums [5,2,3,1] 输出:[1,2,3,5] 输入:nums [5,1,1,2,0,0] 输出:[0,0,1,1,2,5] 详细思路直接看我录制的视频吧 算法-快速排序_哔哩哔哩_bilibili class Soluti…...

SSM项目-博客系统

在线体验项目:登陆页面 项目连接:huhublog_ssm: 个人博客系统 技术栈:SpringBoot、SpringMVC、Mybatis、Redis、JQuery、Ajax、Json (gitee.com) 1.项目技术点分析 SpringBoot、SpringWeb(SpringMVC)、MyBatis、MySQL(8.x)、Redis(存储验…...

Android Gradle Plugin 编译

1. 源码下载: $ mkdir studio-main $ cd studio-main $ repo init -u https://android.googlesource.com/platform/manifest -b studio-main $ repo sync -c -j4 -q 这个官方网址让下载 studio-master-dev 分支,这个分支很老旧了,我这里直接…...

如何快速掌握水土保持方案编制

1、熟悉水土保持常用的主要法律法规、部委规章、规范性文件及技术规范与标准; 2、了解水土保持方案、监测及验收工作开展的流程; 3、熟悉水土保持方案、监测及验收工作需要收集的资料、现场踏勘注意事项; 4、熟悉常见水土保持工程施工工艺…...

前端笔试---acm模式

前言 之前一直刷力扣,昨天做了小红书笔试,发现是acm模式,不太熟悉,特此总结。其实如果是acm模式就需要自己写一下输入输出。前端一般有两个选择,一个是基于 V8 环境,另一个是基于 node。 V8 // 对于有多…...

国联易安网页防篡改保护系统“渠道招募”启动啦!

作为业内专注于保密与非密领域的分级保护、等级保护、业务连续性安全和大数据安全的领军企业,国联易安网页防篡改保护系统基于“高效同步”、“安全传输”两项技术,具备了独特的“五重防护”新特性,支持网页的全自动发布、网页监控、报警和自…...

JavaScript--WebStorage

目录 WebStorage概述 WebStorage分类 注意: localStorage方法 介绍: 常见方法: 案例演示: sessionStorage方法 介绍: 常见方法: 案例演示: WebStorage概述 WebStorage是HTML5中…...

elementui 的 dialog 常用逻辑总结

菜鸟最近写后台管理系统,发现不管是弹窗、还是编辑、查看、添加等功能,真的代码都差不多,但是每次都要重新写里面的关闭逻辑等,菜鸟就感觉不如搞一个模版,后面只关注于逻辑,其他都直接来这里复制了&#xf…...

ip网络广播系统网络音频解码终端公共广播SV-7101

SV-7101V网络音频终端产品简介 网络广播终端SV-7101V,接收网络音频流,实时解码播放。本设备只有网络广播功能,是一款简单的网络广播终端。提供一路线路输出接功放或有源音箱。 产品特点 ■ 提供固件网络远程升级■ 标准RJ45网络接口&…...

【Winform学习笔记(七)】Winform无边框窗体拖动功能

Winform无边框窗体拖动功能 前言正文1、设置无边框模式2、无边框窗体拖动方法1、通过Panel控件实现窗体移动2、通过窗体事件实现窗体移动3、调用系统API实现窗体移动4、重写WndProc()实现窗体移动 前言 在本文中主要介绍 如何将窗体设置成无边框模式、以及实现无边框窗体拖动功…...

【Nginx】静态资源部署、反向代理、负载均衡

个人简介:Java领域新星创作者;阿里云技术博主、星级博主、专家博主;正在Java学习的路上摸爬滚打,记录学习的过程~ 个人主页:.29.的博客 学习社区:进去逛一逛~ nginx静态资源部署、反向代理、负载均衡 &…...

二、框架篇

框架篇 Spring 1. 基础核心技术 第1章-Spring的模块与应用场景 第2章-Spring基于XML配置的容器 第3章-Spring基于注解配置的容器 第4章-Spring基于Java配置的容器 第5章-Spring三种配置方式的混合和迁移 第6章-Spring同类型多个Bean的注入 第7章-Spring的Bean生命周期…...

[LitCTF 2023]Http pro max plus

打开环境后提示说,只允许在本地访问,本地访问,还是想到了XFF字段 好家伙的,直接被嘲讽,还是了解太少了,都不知道还有没有其他方式可以控制ip地址信息 经过查看wp,得知一种新的方式 Client-IP …...

科技的成就(四十九)

381、机器人 Unimate 诞生 "1961 年,第一款工业机器人 Unimate 诞生。工程师恩格尔伯格受阿西莫夫小说《我,机器人》影响,与发明家德沃尔成立了 Unimation。1961 年,公司的第一台机器 人 Unimate 开始在通用电气新泽西工厂试…...

地理信息系统空间分析实验教程 第三版 第八章示例与练习 学校选址

学校选址 背景 合理的学校空间位置布局有利于学生的上课与生活。学校的选址问题需要考虑地理 E八位置、学生娱乐场所配套设施、与现有学校的距离等因素,从总体上把握这些国素能够确定出适宜性比较好的学校选址区 目的 通过练习,熟悉 ArcGIS 栅格数据…...

opencv35-形态学操作-腐蚀cv2.erode()

形态学,即数学形态学(Mathematical Morphology),是图像处理过程中一个非常重要的研 究方向。形态学主要从图像内提取分量信息,该分量信息通常对于表达和描绘图像的形状具有 重要意义,通常是图像理解时所使用…...

数据结构之栈和队列---c++

栈和队列的简单介绍 栈 栈是一个“先进后出”结构 队列 入队演示 队列是一种“先进先出”的结构 出队演示 接下来我们开始本次的内容 栈实现队列 分析 1.我们可以老老实实的写一个栈然后将所有的接口函数实现出来,最后再进行实现队列,但是显然…...

《网约车运营数据分析实战》学习笔记

这篇文章整理自 接地气的陈老师 x 和鲸社区 | 网约车运营分析 数据分析实战活动业务讲解会【接地气的陈老师】的讲解 活动介绍 假设你是某打车APP的商业数据分析师,为某大区提供日常数据报表。现在大区领导表示:希望你从日常数据监测中,发现…...

PostgreSQL常用函数

PostgreSQL常用函数 内置函数 PostgreSQL 内置函数也称为聚合函数,用于对字符串或数字数据执行处理。 下面是所有通用 PostgreSQL 内置函数的列表: COUNT 函数:用于计算数据库表中的行数。MAX 函数:用于查询某一特定列中最大值…...

决策树和随机森林对比

1.用accuracy来对比 # -*-coding:utf-8-*-""" accuracy来对比决策树和随机森林 """ from sklearn.tree import DecisionTreeClassifier from sklearn.ensemble import RandomForestClassifier from sklearn.datasets import load_wine#(178, 13…...

CS 144 Lab Seven -- putting it all together

CS 144 Lab Seven -- putting it all together 引言测试lab7.ccUDPSocketNetworkInterfaceAdapterTCPSocketLab7main方法子线程 小结 对应课程视频: 【计算机网络】 斯坦福大学CS144课程 Lab Six 对应的PDF: Checkpoint 6: putting it all together 引言 本实验无需进行任何编…...

opencv基础-29 Otsu 处理(图像分割)

Otsu 处理 Otsu 处理是一种用于图像分割的方法,旨在自动找到一个阈值,将图像分成两个类别:前景和背景。这种方法最初由日本学者大津展之(Nobuyuki Otsu)在 1979 年提出 在 Otsu 处理中,我们通过最小化类别内…...

gcc-buildroot-9.3.0 和 gcc-arm-10.3 的区别

gcc-buildroot-9.3.0 和 gcc-arm-10.3 是两个不同的 GCC (GNU Compiler Collection) 版本,主要用于编译 C、C 和其他语言的程序。它们之间的区别主要体现在以下几个方面: 版本号:gcc-buildroot-9.3.0 对应的是 GCC 9.3.0 版本,而 …...

IDEA Run SpringBoot程序步骤原理

这个文章不是高深的原理文章,仅仅是接手一个外部提供的阉割版代码遇到过的一个坑,后来解决了,记录一下。 1、IDEA Run 一个SpringBoot一直失败,提示找不到类,但是maven install成功,并且java -jar能成功ru…...

海康威视摄像头配置RTSP协议访问、onvif协议接入、二次开发SDK接入

一、准备工作 (1)拿到摄像头之后,将摄像头电源线插好,再将网线插入到路由器上。 (2)将自己的笔记本电脑也连接到路由器网络,与摄像头出在同一个局域网。 二、配置摄像头 2.1 激活方式选择 第一次使用设备需要激活,在进行配置。 最简单,最方便的方式是选择浏览器激…...

Android中的Parcelable 接口

Android中的Parcelable 接口 在Android中,Parcelable接口是用于实现对象序列化和反序列化的一种机制。它允许我们将自定义的Java对象转换成一个可传输的二进制数据流,以便在不同组件之间传递数据。通常在Activity之间传递复杂的自定义对象时&#xff0c…...