链表——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 常用逻辑总结
菜鸟最近写后台管理系统,发现不管是弹窗、还是编辑、查看、添加等功能,真的代码都差不多,但是每次都要重新写里面的关闭逻辑等,菜鸟就感觉不如搞一个模版,后面只关注于逻辑,其他都直接来这里复制了…...
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),是图像处理过程中一个非常重要的研 究方向。形态学主要从图像内提取分量信息,该分量信息通常对于表达和描绘图像的形状具有 重要意义,通常是图像理解时所使用…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...
python报错No module named ‘tensorflow.keras‘
是由于不同版本的tensorflow下的keras所在的路径不同,结合所安装的tensorflow的目录结构修改from语句即可。 原语句: from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后: from tensorflow.python.keras.lay…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
认识CMake并使用CMake构建自己的第一个项目
1.CMake的作用和优势 跨平台支持:CMake支持多种操作系统和编译器,使用同一份构建配置可以在不同的环境中使用 简化配置:通过CMakeLists.txt文件,用户可以定义项目结构、依赖项、编译选项等,无需手动编写复杂的构建脚本…...
SQL Server 触发器调用存储过程实现发送 HTTP 请求
文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...
Elastic 获得 AWS 教育 ISV 合作伙伴资质,进一步增强教育解决方案产品组合
作者:来自 Elastic Udayasimha Theepireddy (Uday), Brian Bergholm, Marianna Jonsdottir 通过搜索 AI 和云创新推动教育领域的数字化转型。 我们非常高兴地宣布,Elastic 已获得 AWS 教育 ISV 合作伙伴资质。这一重要认证表明,Elastic 作为 …...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...

