Java数据结构和算法(七)——链表

  前面博客咱们在讲解数组中,知道数组做为数据存储结构有必定的缺陷。在无序数组中,搜索性能差,在有序数组中,插入效率又很低,并且这两种数组的删除效率都很低,而且数组在建立后,其大小是固定了,设置的过大会形成内存的浪费,太小又不能知足数据量的存储。node

  本篇博客咱们将讲解一种新型的数据结构——链表。咱们知道数组是一种通用的数据结构,能用来实现栈、队列等不少数据结构。而链表也是一种使用普遍的通用数据结构,它也能够用来做为实现栈、队列等数据结构的基础,基本上除非须要频繁的经过下标来随机访问各个数据,不然不少使用数组的地方均可以用链表来代替。数组

  可是咱们须要说明的是,链表是不能解决数据存储的全部问题的,它也有它的优势和缺点。本篇博客咱们介绍几种常见的链表,分别是单向链表、双端链表、有序链表、双向链表以及有迭代器的链表。而且会讲解一下抽象数据类型(ADT)的思想,如何用 ADT 描述栈和队列,如何用链表代替数组来实现栈和队列。数据结构

一、链表(Linked List)

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

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

  使用链表结构能够克服数组链表须要预先知道数据大小的缺点,链表结构能够充分利用计算机内存空间,实现灵活的内存动态管理。可是链表失去了数组随机读取的优势,同时链表因为增长告终点的指针域,空间开销比较大。工具

 

二、单向链表(Single-Linked List

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

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

  

  在表头增长节点:this

  

  删除节点:spa

  

  ①、单向链表的具体实现

  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,找不到返回null
 44     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     //删除指定的元素,删除成功返回true
 59     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 }
View Code

   测试:设计

 1 @Test
 2 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 }
View Code

  打印结果:

  

  ②、用单向链表实现栈

  栈的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 }
View Code  

四、双端链表

  对于单项链表,咱们若是想在尾部添加一个节点,那么必须从头部一直遍历到尾部,找到尾节点,而后在尾节点后面插入一个节点。这样操做很麻烦,若是咱们在设计链表的时候多个对尾节点的引用,那么会简单不少。

  

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

  ①、双端链表的具体实现

  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,失败返回false
 52     public boolean deleteHead(){
 53         if(size == 0){//当前链表节点数为0
 54             return false;
 55         }
 56         if(head.next == null){//当前链表节点数为1
 57             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 }
View Code

  ②、用双端链表实现队列

 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 }
View Code

 

五、抽象数据类型(ADT)

  在介绍抽象数据类型的时候,咱们先看看什么是数据类型,听到这个词,在Java中咱们可能首先会想到像 int,double这样的词,这是Java中的基本数据类型,一个数据类型会涉及到两件事:

  ①、拥有特定特征的数据项

  ②、在数据上容许的操做

  好比Java中的int数据类型,它表示整数,取值范围为:-2147483648~2147483647,还能使用各类操做符,+、-、*、/ 等对其操做。数据类型容许的操做是它自己不可分离的部分,理解类型包括理解什么样的操做能够应用在该类型上。

  那么当年设计计算机语言的人,为何会考虑到数据类型?

  咱们先看这样一个例子,好比,你们都须要住房子,也都但愿房子越大越好。但显然,没有钱,考虑房子没有意义。因而就出现了各类各样的商品房,有别墅的、复式的、错层的、单间的……甚至只有两平米的胶囊房间。这样作的意义是知足不一样人的须要。

  一样,在计算机中,也存在相同的问题。计算1+1这样的表达式不须要开辟很大的存储空间,不须要适合小数甚至字符运算的内存空间。因而计算机的研究者们就考虑,要对数据进行分类,分出来多种数据类型。好比int,好比float。

  虽然不一样的计算机有不一样的硬件系统,但实际上高级语言编写者才无论程序运行在什么计算机上,他们的目的就是为了实现整形数字的运算,好比a+b等。他们才不关心整数在计算机内部是如何表示的,也无论CPU是如何计算的。因而咱们就考虑,不管什么计算机、什么语言都会面临相似的整数运算,咱们能够考虑将其抽象出来。抽象是抽取出事物具备的广泛性本质,是对事物的一个归纳,是一种思考问题的方式。

  抽象数据类型(ADT)是指一个数学模型及定义在该模型上的一组操做。它仅取决于其逻辑特征,而与计算机内部如何表示和实现无关。好比刚才说得整型,各个计算机,无论大型机、小型机、PC、平板电脑甚至智能手机,都有“整型”类型,也须要整形运算,那么整型其实就是一个抽象数据类型。  

  更普遍一点的,好比咱们刚讲解的栈和队列这两种数据结构,咱们分别使用了数组和链表来实现,好比栈,对于使用者只须要知道pop()和push()方法或其它方法的存在以及如何使用便可,使用者不须要知道咱们是使用的数组或是链表来实现的。

  ADT的思想能够做为咱们设计工具的理念,好比咱们须要存储数据,那么就从考虑须要在数据上实现的操做开始,须要存取最后一个数据项吗?仍是第一个?仍是特定值的项?仍是特定位置的项?回答这些问题会引出ADT的定义,只有完整的定义了ADT后,才应该考虑实现的细节。

  这在咱们Java语言中的接口设计理念是想通的。

 

六、有序链表

  前面的链表实现插入数据都是无序的,在有些应用中须要链表中的数据有序,这称为有序链表。

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

 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 }
View Code

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

 

七、有序链表和无序数组组合排序

  好比有一个无序数组须要排序,前面咱们在讲解冒泡排序、选择排序、插入排序这三种简单的排序时,须要的时间级别都是O(N2)。

  如今咱们讲解了有序链表以后,对于一个无序数组,咱们先将数组元素取出,一个一个的插入到有序链表中,而后将他们从有序链表中一个一个删除,从新放入数组,那么数组就会排好序了。和插入排序同样,若是插入了N个新数据,那么进行大概N2/4次比较。可是相对于插入排序,每一个元素只进行了两次排序,一次从数组到链表,一次从链表到数组,大概须要2*N次移动,而插入排序则须要N2次移动,

  效率确定是比前面讲的简单排序要高,可是缺点就是须要开辟差很少两倍的空间,并且数组和链表必须在内存中同时存在,若是有现成的链表能够用,那么这种方法仍是挺好的。

 

八、双向链表

  咱们知道单向链表只能从一个方向遍历,那么双向链表它能够从两个方向遍历。

  

  具体代码实现:

  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 }
View Code

  咱们也能够用双向链表来实现双端队列,这里就不作具体代码演示了。

九、总结

  上面咱们讲了各类链表,每一个链表都包括一个LinikedList对象和许多Node对象,LinkedList对象一般包含头和尾节点的引用,分别指向链表的第一个节点和最后一个节点。而每一个节点对象一般包含数据部分data,以及对上一个节点的引用prev和下一个节点的引用next,只有下一个节点的引用称为单向链表,两个都有的称为双向链表。next值为null则说明是链表的结尾,若是想找到某个节点,咱们必须从第一个节点开始遍历,不断经过next找到下一个节点,直到找到所须要的。栈和队列都是ADT,能够用数组来实现,也能够用链表实现。

相关文章
相关标签/搜索