Java数据结构和算法(七)——链表
前面博客我们在讲解数组中,知道数组作为数据存储结构有一定的缺陷。在无序数组中,搜索性能差,在有序数组中,插入效率又很低,而且这两种数组的删除效率都很低,并且数组在创建后,其大小是固定了,设置的过大会造成内存的浪费,过小又不能满足数据量的存储。
本篇博客我们将讲解一种新型的数据结构——链表。我们知道数组是一种通用的数据结构,能用来实现栈、队列等很多数据结构。而链表也是一种使用广泛的通用数据结构,它也可以用来作为实现栈、队列等数据结构的基础,基本上除非需要频繁的通过下标来随机访问各个数据,否则很多使用数组的地方都可以用链表来代替。
但是我们需要说明的是,链表是不能解决数据存储的所有问题的,它也有它的优点和缺点。本篇博客我们介绍几种常见的链表,分别是单向链表、双端链表、有序链表、双向链表以及有迭代器的链表。并且会讲解一下抽象数据类型(ADT)的思想,如何用 ADT 描述栈和队列,如何用链表代替数组来实现栈和队列。
回到顶部
1、链表(Linked List)
链表通常由一连串节点组成,每个节点包含任意的实例数据(data fields)和一或两个用来指向上一个/或下一个节点的位置的链接("links")
链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。
使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。
回到顶部
2、单向链表(Single-Linked List)
单链表是链表中结构最简单的。一个单链表的节点(Node)分为两个部分,第一个部分(data)保存或者显示关于节点的信息,另一个部分存储下一个节点的地址。最后一个节点存储地址的部分指向空值。
单向链表只可向一个方向遍历,一般查找一个节点的时候需要从第一个节点开始每次访问下一个节点,一直访问到需要的位置。而插入一个节点,对于单向链表,我们只提供在链表头插入,只需要将当前插入的节点设置为头节点,next指向原头节点即可。删除一个节点,我们将该节点的上一个节点的next指向该节点的下一个节点。
在表头增加节点:
删除节点:
①、单向链表的具体实现
1 package com.ys.datastructure;2 3 public class SingleLinkedList {4 private int size;//链表节点的个数5 private Node head;//头节点6 7 public SingleLinkedList(){8 size = 0;9 head = null;10 }11 12 //链表的每个节点类13 private class Node{14 private Object data;//每个节点的数据15 private Node next;//每个节点指向下一个节点的连接16 17 public Node(Object data){18 this.data = data;19 }20 }21 22 //在链表头添加元素23 public Object addHead(Object obj){24 Node newHead = new Node(obj);25 if(size == 0){26 head = newHead;27 }else{28 newHead.next = head;29 head = newHead;30 }31 size++;32 return obj;33 }34 35 //在链表头删除元素36 public Object deleteHead(){37 Object obj = head.data;38 head = head.next;39 size--;40 return obj;41 }42 43 //查找指定元素,找到了返回节点Node,找不到返回null44 public Node find(Object obj){45 Node current = head;46 int tempSize = size;47 while(tempSize > 0){48 if(obj.equals(current.data)){49 return current;50 }else{51 current = current.next;52 }53 tempSize--;54 }55 return null;56 }57 58 //删除指定的元素,删除成功返回true59 public boolean delete(Object value){60 if(size == 0){61 return false;62 }63 Node current = head;64 Node previous = head;65 while(current.data != value){66 if(current.next == null){67 return false;68 }else{69 previous = current;70 current = current.next;71 }72 }73 //如果删除的节点是第一个节点74 if(current == head){75 head = current.next;76 size--;77 }else{//删除的节点不是第一个节点78 previous.next = current.next;79 size--;80 }81 return true;82 }83 84 //判断链表是否为空85 public boolean isEmpty(){86 return (size == 0);87 }88 89 //显示节点信息90 public void display(){91 if(size >0){92 Node node = head;93 int tempSize = size;94 if(tempSize == 1){//当前链表只有一个节点95 System.out.println("["+node.data+"]");96 return;97 }98 while(tempSize>0){99 if(node.equals(head)){
100 System.out.print("["+node.data+"->");
101 }else if(node.next == null){
102 System.out.print(node.data+"]");
103 }else{
104 System.out.print(node.data+"->");
105 }
106 node = node.next;
107 tempSize--;
108 }
109 System.out.println();
110 }else{//如果链表一个节点都没有,直接打印[]
111 System.out.println("[]");
112 }
113
114 }
115
116 }
测试:
1 @Test2 public void testSingleLinkedList(){3 SingleLinkedList singleList = new SingleLinkedList();4 singleList.addHead("A");5 singleList.addHead("B");6 singleList.addHead("C");7 singleList.addHead("D");8 //打印当前链表信息9 singleList.display();
10 //删除C
11 singleList.delete("C");
12 singleList.display();
13 //查找B
14 System.out.println(singleList.find("B"));
15 }
打印结果:
②、用单向链表实现栈
栈的pop()方法和push()方法,对应于链表的在头部删除元素deleteHead()以及在头部增加元素addHead()。
1 package com.ys.datastructure;2 3 public class StackSingleLink {4 private SingleLinkedList link;5 6 public StackSingleLink(){7 link = new SingleLinkedList();8 }9
10 //添加元素
11 public void push(Object obj){
12 link.addHead(obj);
13 }
14
15 //移除栈顶元素
16 public Object pop(){
17 Object obj = link.deleteHead();
18 return obj;
19 }
20
21 //判断是否为空
22 public boolean isEmpty(){
23 return link.isEmpty();
24 }
25
26 //打印栈内元素信息
27 public void display(){
28 link.display();
29 }
30
31 }
回到顶部
4、双端链表
对于单项链表,我们如果想在尾部添加一个节点,那么必须从头部一直遍历到尾部,找到尾节点,然后在尾节点后面插入一个节点。这样操作很麻烦,如果我们在设计链表的时候多个对尾节点的引用,那么会简单很多。
注意和后面将的双向链表的区别!!!
①、双端链表的具体实现
1 package com.ys.link;2 3 public class DoublePointLinkedList {4 private Node head;//头节点5 private Node tail;//尾节点6 private int size;//节点的个数7 8 private class Node{9 private Object data;10 private Node next;11 12 public Node(Object data){13 this.data = data;14 }15 }16 17 public DoublePointLinkedList(){18 size = 0;19 head = null;20 tail = null;21 }22 23 //链表头新增节点24 public void addHead(Object data){25 Node node = new Node(data);26 if(size == 0){//如果链表为空,那么头节点和尾节点都是该新增节点27 head = node;28 tail = node;29 size++;30 }else{31 node.next = head;32 head = node;33 size++;34 }35 }36 37 //链表尾新增节点38 public void addTail(Object data){39 Node node = new Node(data);40 if(size == 0){//如果链表为空,那么头节点和尾节点都是该新增节点41 head = node;42 tail = node;43 size++;44 }else{45 tail.next = node;46 tail = node;47 size++;48 }49 }50 51 //删除头部节点,成功返回true,失败返回false52 public boolean deleteHead(){53 if(size == 0){//当前链表节点数为054 return false;55 }56 if(head.next == null){//当前链表节点数为157 head = null;58 tail = null;59 }else{60 head = head.next;61 }62 size--;63 return true;64 }65 //判断是否为空66 public boolean isEmpty(){67 return (size ==0);68 }69 //获得链表的节点个数70 public int getSize(){71 return size;72 }73 74 //显示节点信息75 public void display(){76 if(size >0){77 Node node = head;78 int tempSize = size;79 if(tempSize == 1){//当前链表只有一个节点80 System.out.println("["+node.data+"]");81 return;82 }83 while(tempSize>0){84 if(node.equals(head)){85 System.out.print("["+node.data+"->");86 }else if(node.next == null){87 System.out.print(node.data+"]");88 }else{89 System.out.print(node.data+"->");90 }91 node = node.next;92 tempSize--;93 }94 System.out.println();95 }else{//如果链表一个节点都没有,直接打印[]96 System.out.println("[]");97 }98 }99
100 }
②、用双端链表实现队列
1 package com.ys.link;2 3 public class QueueLinkedList {4 5 private DoublePointLinkedList dp;6 7 public QueueLinkedList(){8 dp = new DoublePointLinkedList();9 }
10 public void insert(Object data){
11 dp.addTail(data);
12 }
13
14 public void delete(){
15 dp.deleteHead();
16 }
17
18 public boolean isEmpty(){
19 return dp.isEmpty();
20 }
21
22 public int getSize(){
23 return dp.getSize();
24 }
25
26 public void display(){
27 dp.display();
28 }
29
30 }
回到顶部
5、抽象数据类型(ADT)
在介绍抽象数据类型的时候,我们先看看什么是数据类型,听到这个词,在Java中我们可能首先会想到像 int,double这样的词,这是Java中的基本数据类型,一个数据类型会涉及到两件事:
①、拥有特定特征的数据项
②、在数据上允许的操作
比如Java中的int数据类型,它表示整数,取值范围为:-2147483648~2147483647,还能使用各种操作符,+、-、*、/ 等对其操作。数据类型允许的操作是它本身不可分离的部分,理解类型包括理解什么样的操作可以应用在该类型上。
那么当年设计计算机语言的人,为什么会考虑到数据类型?
我们先看这样一个例子,比如,大家都需要住房子,也都希望房子越大越好。但显然,没有钱,考虑房子没有意义。于是就出现了各种各样的商品房,有别墅的、复式的、错层的、单间的……甚至只有两平米的胶囊房间。这样做的意义是满足不同人的需要。
同样,在计算机中,也存在相同的问题。计算1+1这样的表达式不需要开辟很大的存储空间,不需要适合小数甚至字符运算的内存空间。于是计算机的研究者们就考虑,要对数据进行分类,分出来多种数据类型。比如int,比如float。
虽然不同的计算机有不同的硬件系统,但实际上高级语言编写者才不管程序运行在什么计算机上,他们的目的就是为了实现整形数字的运算,比如a+b等。他们才不关心整数在计算机内部是如何表示的,也不管CPU是如何计算的。于是我们就考虑,无论什么计算机、什么语言都会面临类似的整数运算,我们可以考虑将其抽象出来。抽象是抽取出事物具有的普遍性本质,是对事物的一个概括,是一种思考问题的方式。
抽象数据类型(ADT)是指一个数学模型及定义在该模型上的一组操作。它仅取决于其逻辑特征,而与计算机内部如何表示和实现无关。比如刚才说得整型,各个计算机,不管大型机、小型机、PC、平板电脑甚至智能手机,都有“整型”类型,也需要整形运算,那么整型其实就是一个抽象数据类型。
更广泛一点的,比如我们刚讲解的栈和队列这两种数据结构,我们分别使用了数组和链表来实现,比如栈,对于使用者只需要知道pop()和push()方法或其它方法的存在以及如何使用即可,使用者不需要知道我们是使用的数组或是链表来实现的。
ADT的思想可以作为我们设计工具的理念,比如我们需要存储数据,那么就从考虑需要在数据上实现的操作开始,需要存取最后一个数据项吗?还是第一个?还是特定值的项?还是特定位置的项?回答这些问题会引出ADT的定义,只有完整的定义了ADT后,才应该考虑实现的细节。
这在我们Java语言中的接口设计理念是想通的。
回到顶部
6、有序链表
前面的链表实现插入数据都是无序的,在有些应用中需要链表中的数据有序,这称为有序链表。
在有序链表中,数据是按照关键值有序排列的。一般在大多数需要使用有序数组的场合也可以使用有序链表。有序链表优于有序数组的地方是插入的速度(因为元素不需要移动),另外链表可以扩展到全部有效的使用内存,而数组只能局限于一个固定的大小中。
1 package com.ys.datastructure;2 3 public class OrderLinkedList {4 private Node head;5 6 private class Node{7 private int data;8 private Node next;9
10 public Node(int data){
11 this.data = data;
12 }
13 }
14
15 public OrderLinkedList(){
16 head = null;
17 }
18
19 //插入节点,并按照从小打到的顺序排列
20 public void insert(int value){
21 Node node = new Node(value);
22 Node pre = null;
23 Node current = head;
24 while(current != null && value > current.data){
25 pre = current;
26 current = current.next;
27 }
28 if(pre == null){
29 head = node;
30 head.next = current;
31 }else{
32 pre.next = node;
33 node.next = current;
34 }
35 }
36
37 //删除头节点
38 public void deleteHead(){
39 head = head.next;
40 }
41
42 public void display(){
43 Node current = head;
44 while(current != null){
45 System.out.print(current.data+" ");
46 current = current.next;
47 }
48 System.out.println("");
49 }
50
51 }
在有序链表中插入和删除某一项最多需要O(N)次比较,平均需要O(N/2)次,因为必须沿着链表上一步一步走才能找到正确的插入位置,然而可以最快速度删除最值,因为只需要删除表头即可,如果一个应用需要频繁的存取最小值,且不需要快速的插入,那么有序链表是一个比较好的选择方案。比如优先级队列可以使用有序链表来实现。
回到顶部
7、有序链表和无序数组组合排序
比如有一个无序数组需要排序,前面我们在讲解冒泡排序、选择排序、插入排序这三种简单的排序时,需要的时间级别都是O(N2)。
现在我们讲解了有序链表之后,对于一个无序数组,我们先将数组元素取出,一个一个的插入到有序链表中,然后将他们从有序链表中一个一个删除,重新放入数组,那么数组就会排好序了。和插入排序一样,如果插入了N个新数据,那么进行大概N2/4次比较。但是相对于插入排序,每个元素只进行了两次排序,一次从数组到链表,一次从链表到数组,大概需要2*N次移动,而插入排序则需要N2次移动,
效率肯定是比前面讲的简单排序要高,但是缺点就是需要开辟差不多两倍的空间,而且数组和链表必须在内存中同时存在,如果有现成的链表可以用,那么这种方法还是挺好的。
回到顶部
8、双向链表
我们知道单向链表只能从一个方向遍历,那么双向链表它可以从两个方向遍历。
具体代码实现:
1 package com.ys.datastructure;2 3 public class TwoWayLinkedList {4 private Node head;//表示链表头5 private Node tail;//表示链表尾6 private int size;//表示链表的节点个数7 8 private class Node{9 private Object data;10 private Node next;11 private Node prev;12 13 public Node(Object data){14 this.data = data;15 }16 }17 18 public TwoWayLinkedList(){19 size = 0;20 head = null;21 tail = null;22 }23 24 //在链表头增加节点25 public void addHead(Object value){26 Node newNode = new Node(value);27 if(size == 0){28 head = newNode;29 tail = newNode;30 size++;31 }else{32 head.prev = newNode;33 newNode.next = head;34 head = newNode;35 size++;36 }37 }38 39 //在链表尾增加节点40 public void addTail(Object value){41 Node newNode = new Node(value);42 if(size == 0){43 head = newNode;44 tail = newNode;45 size++;46 }else{47 newNode.prev = tail;48 tail.next = newNode;49 tail = newNode;50 size++;51 }52 }53 54 //删除链表头55 public Node deleteHead(){56 Node temp = head;57 if(size != 0){58 head = head.next;59 head.prev = null;60 size--;61 }62 return temp;63 }64 65 //删除链表尾66 public Node deleteTail(){67 Node temp = tail;68 if(size != 0){69 tail = tail.prev;70 tail.next = null;71 size--;72 }73 return temp;74 }75 76 //获得链表的节点个数77 public int getSize(){78 return size;79 }80 //判断链表是否为空81 public boolean isEmpty(){82 return (size == 0);83 }84 85 //显示节点信息86 public void display(){87 if(size >0){88 Node node = head;89 int tempSize = size;90 if(tempSize == 1){//当前链表只有一个节点91 System.out.println("["+node.data+"]");92 return;93 }94 while(tempSize>0){95 if(node.equals(head)){96 System.out.print("["+node.data+"->");97 }else if(node.next == null){98 System.out.print(node.data+"]");99 }else{
100 System.out.print(node.data+"->");
101 }
102 node = node.next;
103 tempSize--;
104 }
105 System.out.println();
106 }else{//如果链表一个节点都没有,直接打印[]
107 System.out.println("[]");
108 }
109
110 }
111 }
我们也可以用双向链表来实现双端队列,这里就不做具体代码演示了。
回到顶部
9、总结
上面我们讲了各种链表,每个链表都包括一个LinikedList对象和许多Node对象,LinkedList对象通常包含头和尾节点的引用,分别指向链表的第一个节点和最后一个节点。而每个节点对象通常包含数据部分data,以及对上一个节点的引用prev和下一个节点的引用next,只有下一个节点的引用称为单向链表,两个都有的称为双向链表。next值为null则说明是链表的结尾,如果想找到某个节点,我们必须从第一个节点开始遍历,不断通过next找到下一个节点,直到找到所需要的。栈和队列都是ADT,可以用数组来实现,也可以用链表实现。
作者:YSOcean
出处:http://www.cnblogs.com/ysocean/
Java数据结构和算法(七)——链表相关推荐
- Java数据结构和算法(四)--链表
日常开发中,数组和集合使用的很多,而数组的无序插入和删除效率都是偏低的,这点在学习ArrayList源码的时候就知道了,因为需要把要 插入索引后面的所以元素全部后移一位. 而本文会详细讲解链表,可以解 ...
- Java数据结构与算法 day02 链表
文章目录 第三章 链表 单链表介绍和内存布局 单链表创建和遍历的分析实现 添加(创建)过程 遍历过程 代码实现 单链表按顺序插入节点 单链表节点的修改 单链表节点的删除和小结 单链表面试题 新浪面试题 ...
- Java 数据结构与算法面试 链表
手写链表的Java实现 package test;class Node{ // 数据域private Object ele; // 指针域private Node node; // 默认构造器,标准构 ...
- 一文通数据结构与算法之——链表+常见题型与解题策略+Leetcode经典题
文章目录 1 链表 1.1 常见题型及解题策略 1.1.1 LeetCode中关于链表的题目有以下五种类型题: 1.1.2 解题策略 1.2 链表的基本内容 1.2.1 链表的基本结构: 1.2.2 ...
- java数据结构与算法之顺序表与链表深入分析
转载请注明出处(万分感谢!): http://blog.csdn.net/javazejian/article/details/52953190 出自[zejian的博客] 关联文章: java数据结 ...
- java数据结构与算法之双链表设计与实现
转载请注明出处(万分感谢!): http://blog.csdn.net/javazejian/article/details/53047590 出自[zejian的博客] 关联文章: java数据结 ...
- Java数据结构和算法(一)——简介
本系列博客我们将学习数据结构和算法,为什么要学习数据结构和算法,这里我举个简单的例子. 编程好比是一辆汽车,而数据结构和算法是汽车内部的变速箱.一个开车的人不懂变速箱的原理也是能开车的,同理一个不懂数 ...
- JAVA数据结构与算法【简单介绍】
前几天去面一个大厂,面试官特别好,面试官说到,我们的学习不能本末倒置,数据结构和算法是程序的基础,如果数据结构你没有学好,你真正意义上不算会写代码.你的代码是各处粘贴,杂乱无章的. 由于现在大多用JA ...
- java算法概述,Java数据结构与算法基础(一)概述与线性结构
Java数据结构与算法基础(二)递归算法 Java数据结构与算法基础(一)概述与线性结构 学习目的:为了能更顺畅的读很多底层API代码和拓宽解决问题的思路 一.数据结构概述 1.数据结构是什么?数据与 ...
- Java数据结构与算法(二)
Java数据结构与算法(二) 第六章 递归 1 递归应用场景 2 递归的概念 3 递归调用机制 4 递归能解决什么样的问题 5 递归需要遵守的重要规则 6 递归-迷宫问题 6.1 迷宫问题 6.2 代 ...
最新文章
- C++的黑科技之进制转换
- 简单的信誉算法 js处理
- 蓝桥杯基础模块9:IO口扩展与存储器映射
- 前端学习(221):字体属性
- POJ2260 ZOJ1949 UVA541 Error Correction题解
- oracle+110个常用函数
- ncnn数据归一化和结果解析
- echart legends换行固定数量显示
- 2022年陕西省职业院校技能大赛中职组网络安全赛项规程
- 颜色中英文对照表颜色名字色彩名称
- Python3.6支付宝账单爬虫
- Android Studio 关于android resource linking failed的报错解决方法
- Idea的类注释和方法注释
- 一款简易低成本智能割草机的制作——硬件篇
- [附源码]计算机毕业设计JAVA 停车场管理系统
- Java程序流程控制(符号函数sgn、闰年判断)
- Wireshark网络分析实战笔记(二)显示过滤器
- 2022年CVPR挑战赛
- STA分析(六) cross talk and noise
- STM32+RS485+Modbus-RTU(主机模式+从机模式)-标准库/HAL库开发
热门文章
- 非spring环境中配置文件工具
- java 并发编程第七章:取消和关闭
- TCP/IP协议簇分层详解---转
- 【开发工具】Jupyter Notebook 的快捷键
- php中的魔术函数以及魔术常量
- Algorithms_二叉树的层次遍历(广度优先)
- Elasticsearch-05Elasticsearch之查询与过滤
- mysql用户名长度_如何增加PhpMyAdmin / mysql用户帐户的用户名长度?
- w7系统装天联高级版服务器,w7系统有几个版本你都知道吗?
- 高德地图区域线显示_护航国庆假期,助力重点区域精细化管理——扬州交警与高德地图联合推出“全境智能”系统...