链表——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,可以用数组来实现,也可以用链表实现。

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

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

相关文章

2023下半年软考初级网络管理员报名入口-报名流程-备考方法

软考初级网络管理员2023下半年考试时间: 2023年下半年软考初级网络管理员的考试时间为11月4日、5日。考试时间在全国各地一致,建议考生提前备考。共分两科,第一科基础知识考试具体时间为9:00到11:30;第二科应用技术考试具体时间为…

AWS 中文入门开发教学 49- S3 - 区域间复制

知识点 S3 存储桶内容在全球区域间进行复制官网 https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/replication.html 实战演习 在东京区建立存储桶 Name: woyaofuzhi启用版本控制(完成区域间复制必须开启版本控制) 在新加坡区建立存储桶 Name: woyaofuzhibac…

Vue2源码分析-环境搭建

安装rollup 项目初始化 npm init -y安装pnpm npm i -g pnpm安装rollup以及相关插件 pnpm i rollup rollup/plugin-babel babel/core babel/preset-env --save-dev在根目录创建rollup.config.js文件,并且配置如下 import babel from "rollup/plugin-babel…

配置固定二级子域名远程访问内网群晖NAS 7.X版 【内网穿透】——“cpolar内网穿透”

配置固定二级子域名远程访问内网群晖NAS 7.X版 【内网穿透】 文章目录 配置固定二级子域名远程访问内网群晖NAS 7.X版 【内网穿透】前言1. 创建一条固定数据隧道2. 找到“保留二级子域名”栏位3. 重新编辑之前建立的临时数据隧道4. 进入“在线隧道列表”页面5. 在其他浏览器访问…

基于 Emscripten + WebAssembly 实现浏览器操作 Excel

一、为什么要造这个轮子 【C】使用WebAssembly在浏览器端操作Excel_wasm文件用什么打开_你的薄荷醇的博客-CSDN博客使用WebAssembly在浏览器端操作Excel_wasm文件用什么打开https://blog.csdn.net/weixin_44305576/article/details/125545900?ops_request_misc%257B%2522requ…

【Redis】——AOF持久化

什么是AOF日志 AOF日志是redis为数据的持久化提供了的一个技术,日志里面记录着执行redis写命令。每当redis执行一条写命令的时候,就会将该命令记录 到AOF日志当中。当redis启动的时候,可以加载AOF日志中的所有指令,并执行这些指令恢复所有的…

FFmpeg将编码后数据保存成mp4

以下测试代码实现的功能是:持续从内存块中获取原始数据,然后依次进行解码、编码、最后保存成mp4视频文件。 可保存成单个视频文件,也可指定每个视频文件的总帧数,保存多个视频文件。 为了便于查看和修改,这里将可独立的…

webpack基础知识十:与webpack类似的工具还有哪些?区别?

一、模块化工具 模块化是一种处理复杂系统分解为更好的可管理模块的方式 可以用来分割,组织和打包应用。每个模块完成一个特定的子功能,所有的模块按某种方法组装起来,成为一个整体(bundle) 在前端领域中,并非只有webpack这一款…

chaitin-Nginx+Docker

Nginx实战 任务一 1、源码包安装NGINX A,搭建Web Server,任意HTML页面,其8080端口提供Web访问服务,截图成功访问http(s)😕/[Server1]:8080并且回显Web页面 官网地址:http://nginx.org/en/download.html 步骤…

MySQL中的视图

系列文章目录 MySQL常见的几种约束 MySQL中的函数 MySQL中的事务 文章目录 系列文章目录前言一、视图的概念二、视图的好处三、SQL展示总结 前言 例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习&…

十二、ESP32控制步进电机

1. 运行效果 2. 步进电机 最大特点:能够控制旋转一定的角度 3. 步进电机原理

【vue】vue基础知识

1、插值表达式&属性绑定 <!--template展示给用户&#xff0c;相当于MVVM模式中的V--> <template><div class"first_div">//插值表达式<p>{{ message }}</p>//这里的参数是从父组件的template里传过来的<p>{{data_1}}</p…

超详情的开源知识库管理系统- mm-wiki的安装和使用

背景&#xff1a;最近公司需要一款可以记录公司内部文档信息&#xff0c;一些只是累计等&#xff0c;通过之前的经验积累&#xff0c;立马想到了 mm-wiki&#xff0c;然后就给公司搭建了一套&#xff0c;分享一下安装和使用说明&#xff1a; 当前市场上众多的优秀的文档系统百…

Demystifying Prompts in Language Models via Perplexity Estimation

Demystifying Prompts in Language Models via Perplexity Estimation 原文链接 Gonen H, Iyer S, Blevins T, et al. Demystifying prompts in language models via perplexity estimation[J]. arXiv preprint arXiv:2212.04037, 2022. 简单来说就是作者通过在不同LLM和不同…

VLT:Vision-Language Transformer用于引用的视觉语言转换和查询生成分割

摘要 在这项工作中&#xff0c;我们解决了引用分割的挑战性任务。引用分割中的查询表达式通常通过描述目标对象与其他对象的关系来表示目标对象。因此&#xff0c;为了在图像中的所有实例中找到目标实例&#xff0c;模型必须对整个图像有一个整体的理解。为了实现这一点&#…

Flutter 让软键盘不再自动弹起

1、问题说明&#xff1a; 在开发中&#xff0c;经常遇到这种事&#xff0c;一个页面有输入框&#xff0c;点击输入框后&#xff0c;会弹起软键盘&#xff0c;同时输入框会聚焦&#xff0c;手动收起软键盘后&#xff0c;点击另一个按钮前往下一个页面或者显示一个弹窗&#xff0…

一百四十一、Kettle——kettle8.2在Windows本地开启carte服务以及配置子服务器

一、目的 在kettle建好共享资源库后&#xff0c;为了给在服务器上部署kettle的carte服务躺雷&#xff0c;先在Windows本地测试一下怎么玩carte服务 二、Kettle版本以及在Windows本地安装路径 kettle版本是8.2 pdi-ce-8.2.0.0-342 kettle本地安装路径是D:\j…

Vue + Cesium快速搭建,全流程(最新总结)

方式一&#xff1a;直接引入&#xff08;最简单&#xff09; 1.安装Cesium&#xff08;Vue搭建可以看我上一期的文章&#xff09; npm i cesium -save2.将node_modules\cesium\Build\Cesium文件夹拷贝到项目的public文件中 3.在public\index.html引入Cesium <!DOCTYPE h…

[NOIP2007 普及组] 纪念品分组

[NOIP2007 普及组] 纪念品分组 题目描述 元旦快到了&#xff0c;校学生会让乐乐负责新年晚会的纪念品发放工作。为使得参加晚会的同学所获得 的纪念品价值相对均衡&#xff0c;他要把购来的纪念品根据价格进行分组&#xff0c;但每组最多只能包括两件纪念品&#xff0c; 并且…

百度秋招攻略,百度网申笔试面试详解

百度秋招简介 作为行业巨头&#xff0c;百度向社会提供的岗位一直都是非常吃香的&#xff0c;每年也都有很多考生密切关注&#xff0c;百度发布的招聘广告&#xff0c;以尽可能的让自己进入这家企业工作&#xff0c;实现自己的人生价值。那么百度每年的秋招时间是多久&#xf…