链表——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.hqwc.cn/news/56704.html

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

相关文章

《孙子兵法》是世界上最著名的中国著作,不服来看,趣讲《孙子兵法》第1讲

《孙子兵法》是世界上最著名的中国著作,不服来看!趣讲《孙子兵法》【第1讲】 世界上最有名的中国著作肯定是《孙子兵法》。在著名的美国西点军校是必修课。 在企业管理领域,听一些讲座,经常会引用到《孙子兵法》的一些名句&#…

Java-认识String

目录 一、String概念及创建 1.1 String概念 1.2 String的创建 二、String常用方法 2.1 String对象的比较 2.2 字符串查找 2.3 转化 2.4 字符串替换 2.5 字符串拆分 2.6字符串的截取 2.7 其他操作方法 2.8 字符串修改 三、面试题 一、String概念及创建 1.1 String概念 Java中…

Qt能跨多少个平台?Qt能支持多少个平台?

2023年8月5日,周日下午 目录 Qt所支持的平台更多关于Qt支持的信息 Qt所支持的平台 图中显示的平台都支持。 想要更详细的平台支持信息可以查看:Supported Platforms | Qt 5.15 更多关于Qt支持的信息 Qt - 支持的平台及语言

sentinel组件

目录 定义 4.加SentinelResource,blockHander是超过阈值之后执行的函数 5.设置阈值 6.springboot集成sentinel 定义 1.sentinel知道当前流量大小,在浏览器和后端之间加sentinel控制流量,避免大批量的瞬时请求都达到服务上,将服务压垮 2.…

第一天 什么是CSRF ?

✅作者简介:大家好,我是Cisyam,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Cisyam-Shark的博客 💞当前专栏: 每天一个知识点 ✨特色专…

VSCode如何在行内显示变量值

背景 在调试时,我们希望能够直接在代码行显示变量的值,而不是总是去侧边栏查看,如下这种,y12直接显示在代码行。那么VSCode中如何做呢 设置 VSCode提供了“inline values”设置,但为了速度,默认并没有开…

在 React 中渲染大型数据集的 3 种方法

随着 Web 应用程序变得越来越复杂,我们需要找到有效的方法来优化性能和渲染大型数据集。在 React 应用程序中处理大型数据集时,一次呈现所有数据可能会导致性能不佳和加载时间变慢。 虚拟化是一种通过一次仅呈现数据集的一部分来解决此问题的技术&#…

2023年8月实时获取地图边界数据方法,省市区县街道多级联动【附实时geoJson数据下载】

首先,来看下效果图 在线体验地址:https://geojson.hxkj.vip,并提供实时geoJson数据文件下载 可下载的数据包含省级geojson行政边界数据、市级geojson行政边界数据、区/县级geojson行政边界数据、省市区县街道行政编码四级联动数据&#xff0…

高速过孔同进同出后续来了!影响大不大由你们自己说

高速先生成员---黄刚 话说Chris在上篇文章的结尾留下的悬念,其实在上周的答题里,也有不少粉丝猜到了接下来要验证的内容。我们知道,任何两个结构如果距离变近了,容性就会增加,无论是孔和孔,线和线&#xf…

php-cgi.exe - FastCGI 进程超过了配置的请求超时时限

解决方案一: 处理(php-cgi.exe - FastCGI 进程超过了配置的请求超时时限)的问题 内容转载: 处理(php-cgi.exe - FastCGI 进程超过了配置的请求超时时限)的问题_php技巧_脚本之家 【详细错误】: HTTP 错误 500.0 - Internal Server Error C:…

Rust 编程小技巧摘选(7)

Rust 编程小技巧(7) 1. 结构体 Display trait 结构体的两种形式,对应的成员取法不同; 前者用 self.成员变量名 self.x, self.y;后者用 self.成员索引号 self.0, self.1, self.2, ...... use std::fmt::Display; use std::fmt::Result; us…

SQL Server数据库如何添加Oracle链接服务器(Windows系统)

SQL Server数据库如何添加Oracle链接服务器 一、在添加访问Oracle的组件1.1 下载Oracle的组件 Oracle Provider for OLE DB1.2 注册该组件1.2.1 下载的压缩包解压位置1.2.2 接着用管理员运行Cmd 此处一定要用管理员运行,否则会报错 二、配置环境变量三、 重启SQL Se…