返回顶部

01玩转数据结构_04_最基础的动态数据结构:链表

什么是链表:

我们之前已经学过了 动态数组,栈和 队列 三种我们自己定义的数据结构。它们三者的底层都是依托静态数组(使用resize解决容量问题)。

 

而下面我们学习的链表,它才是真正的动态数据结构

 

为什么链表很重要:

1,它是最简单的动态数据结构,有助于学习后面更加复杂的数据结构。

2,有助于更深入的理解引用 (Java)/  指针(c/c++)。

3,   有助于更深入的理解递归。

4, 辅助组成其他数据结构。

链表基础:

 

数组 和 链表 的对比:

 

 

代码:

 1 package cn.zcb.demo04;
 2 
 3 public class LinkedList <T> {
 4     private class Node{
 5         public T data;//具体数据
 6         public Node next; //引用(指针)
 7 
 8         //Node 节点的构造器
 9         public Node(T data,Node next){
10             this.data  = data;
11             this.next = next;
12         }
13         public Node(T data){
14             this(data,null);
15         }
16         public  Node(){
17             this(null,null);
18         }
19 
20         @Override
21         public String toString(){
22             return this.data.toString();
23         }
24 
25     }
26 
27 
28 }
构建节点!!!

 

在链表中添加元素:

 1 package cn.zcb.demo04;
 2 
 3 public class LinkedList <T> {
 4     private class Node{
 5         public T data;//具体数据
 6         public Node next; //引用(指针)
 7 
 8         //Node 节点的构造器
 9         public Node(T data,Node next){
10             this.data  = data;
11             this.next = next;
12         }
13         public Node(T data){
14             this(data,null);
15         }
16         public  Node(){
17             this(null,null);
18         }
19 
20         @Override
21         public String toString(){
22             return this.data.toString();
23         }
24 
25     }
26 
27     private Node head; //头节点
28     private int size;
29     //LinkedList 的构造器
30 
31     public LinkedList (){  //只需要空参构造器  , Node() 实例化需在LinkedList 内部实现!!!外部无法实现。
32         this.head = null;
33         this.size = 0;
34     }
35     //获取链表中的元素个数
36     public int getSize(){
37         return this.size;
38     }
39     //返回链表是否为空
40     public boolean isEmpty(){
41         return this.size ==0;
42     }
43 
44     //在链表头添加 新的元素 newData
45     public void addFirst(T newData){
46 //        Node newNode = new Node(newData,null);
47 //
48 //        newNode.next = this.head;
49 //        this.head = newNode;
50         //下面是更简洁的写法
51         this.head = new Node(newData,this.head);   //一句话 顶上面三句话。
52         this.size ++;
53     }
54     //在链表 中间 索引(idx)  添加新元素
55     public void insertByIdx(int idx,T newData){
56         if(idx <= 0 || idx > this.size){
57             //在链表头插入
58             this.addFirst(newData);
59             return;
60         }
61         Node tempPtr = new Node();
62         tempPtr = this.head;
63         for (int i =0;i< idx -1 ;i++){
64             tempPtr = tempPtr.next;
65         }
66         Node node = new Node(newData,tempPtr.next);
67         tempPtr.next = node;
68 
69         this.size ++;
70     }
71 
72     //在链表尾部 添加新元素
73     public void addLast(T newData){
74         this.insertByIdx(this.size,newData);
75     }
76 
77     @Override
78     public String toString(){
79         StringBuilder builder = new StringBuilder();
80         builder.append(String.format("LinkList's Data is below: [size:%d] \n", this.size));
81         Node tempPtr = this.head;
82 
83         while (tempPtr !=null){
84             builder.append(tempPtr.data +", ");
85            tempPtr =tempPtr.next;
86         }
87         return builder.toString();
88     }
89 
90 }
在链表中添加元素(在链表头添加,在链表中添加,和在链表尾添加)

 

为链表设立虚拟头节点(重要):

在前面 在链表中添加元素的时候,我们会发现, 操作链表头节点和 操作其他的节点的时候的逻辑会不同。

这时因为,链表头节点的前面已经没有节点了。所以它和其他的节点会有区别。

 

 

这样头节点的处理逻辑 就和其他的节点一致了。 

 

 1 package cn.zcb.demo04;
 2 
 3 public class LinkedList <T> {
 4     private class Node{
 5         public T data;//具体数据
 6         public Node next; //引用(指针)
 7 
 8         //Node 节点的构造器
 9         public Node(T data,Node next){
10             this.data  = data;
11             this.next = next;
12         }
13         public Node(T data){
14             this(data,null);
15         }
16         public  Node(){
17             this(null,null);
18         }
19 
20         @Override
21         public String toString(){
22             return this.data.toString();
23         }
24 
25     }
26 
27     private Node dummyHead; //虚拟头节点
28     private int size;
29     //LinkedList 的构造器
30 
31     public LinkedList (){    //此时,当链表初始化的时候就已经存在一个 虚拟的头节点了。
32         dummyHead = new Node(null,null);
33         this.size = 0;
34     }
35     //获取链表中的元素个数
36     public int getSize(){
37         return this.size;
38     }
39     //返回链表是否为空
40     public boolean isEmpty(){
41         return this.size ==0;
42     }
43     //在链表 中间 索引(idx)  添加新元素
44     public void insertByIdx(int idx,T newData){
45         if(idx < 0 || idx > this.size){
46             throw new IllegalArgumentException("idx 错误!");
47         }
48         Node tempPtr = new Node();
49         tempPtr = this.dummyHead;
50         for (int i =0;i< idx;i++){
51             tempPtr = tempPtr.next;
52         }
53         Node node = new Node(newData,tempPtr.next);
54         tempPtr.next = node;
55 
56         this.size ++;
57     }
58 
59     //在链表头添加 新的元素 newData
60     public void addFirst(T newData){
61         insertByIdx(0,newData);
62     }
63     //在链表尾部 添加新元素
64     public void addLast(T newData){
65         this.insertByIdx(this.size,newData);
66     }
67 
68     @Override
69     public String toString(){
70         StringBuilder builder = new StringBuilder();
71         builder.append(String.format("LinkList's Data is below: [size:%d] \n", this.size));
72         Node tempPtr = this.dummyHead.next;
73 
74         while (tempPtr !=null){
75             builder.append(tempPtr.data +", ");
76            tempPtr =tempPtr.next;
77         }
78         return builder.toString();
79     }
80 
81 }
使用虚拟头节点,可以使得代码更加优雅!!!

注意:有了虚拟头节点,插入时的遍历就不是从真正的head 开始了,而是从虚拟头节点开始遍历。 注:查看时候的遍历是从真正的head 开始的。 

 1 package cn.zcb.demo04;
 2 
 3 public class Test {
 4 
 5     public static void main(String[] args) {
 6         LinkedList<Integer> linkedList = new LinkedList<>();
 7 //        for (int i =0;i<10;i++){
 8 //            linkedList.addFirst(i);
 9 //        }
10 
11 
12         linkedList.addLast(12);
13         linkedList.addLast(11);
14         for (int i=0;i<6;i++){
15             linkedList.addLast(i);
16         }
17         System.out.println(linkedList);
18 
19 
20         linkedList.addFirst(15);
21         linkedList.addFirst(15);
22         linkedList.addFirst(15);
23         linkedList.addFirst(15);
24         System.out.println(linkedList);
25         linkedList.addLast(12);
26         linkedList.addLast(12);
27         linkedList.addLast(12);
28         linkedList.addLast(12);
29         linkedList.addLast(12);
30         System.out.println(linkedList);
31         
32 
33 
34     }
35 
36 
37 
38 
39 }
Test.java

 

总结: 

虚拟头节点为 添加元素 统一了处理逻辑。这是很重要的一点!!!

在链表中 遍历 ,查询 和修改 元素:

  1 package cn.zcb.demo04;
  2 
  3 public class LinkedList <T> {
  4     private class Node{
  5         public T data;//具体数据
  6         public Node next; //引用(指针)
  7 
  8         //Node 节点的构造器
  9         public Node(T data,Node next){
 10             this.data  = data;
 11             this.next = next;
 12         }
 13         public Node(T data){
 14             this(data,null);
 15         }
 16         public  Node(){
 17             this(null,null);
 18         }
 19 
 20         @Override
 21         public String toString(){
 22             return this.data.toString();
 23         }
 24 
 25     }
 26 
 27     private Node dummyHead; //虚拟头节点
 28     private int size;
 29     //LinkedList 的构造器
 30 
 31     public LinkedList (){    //此时,当链表初始化的时候就已经存在一个 虚拟的头节点了。
 32         dummyHead = new Node(null,null);
 33         this.size = 0;
 34     }
 35     //获取链表中的元素个数
 36     public int getSize(){
 37         return this.size;
 38     }
 39     //返回链表是否为空
 40     public boolean isEmpty(){
 41         return this.size ==0;
 42     }
 43     //在链表 中间 索引(idx)  添加新元素
 44     public void insertByIdx(int idx,T newData){
 45         if(idx < 0 || idx > this.size){
 46             throw new IllegalArgumentException("idx 错误!");
 47         }
 48         Node tempPtr = new Node();
 49         tempPtr = this.dummyHead;
 50         for (int i =0;i< idx;i++){
 51             tempPtr = tempPtr.next;
 52         }
 53         Node node = new Node(newData,tempPtr.next);
 54         tempPtr.next = node;
 55 
 56         this.size ++;
 57     }
 58 
 59     //在链表头添加 新的元素 newData
 60     public void addFirst(T newData){
 61         insertByIdx(0,newData);
 62     }
 63     //在链表尾部 添加新元素
 64     public void addLast(T newData){
 65         this.insertByIdx(this.size,newData);
 66     }
 67 
 68     // 获取链表中的元素
 69     public T getByIdx(int idx){
 70         if(idx <0 && idx >= this.size)
 71             throw new IllegalArgumentException("idx 索引错误!!!");
 72 
 73         Node curPtr = dummyHead.next;
 74         for (int i=0;i<idx;i++){
 75             curPtr = curPtr.next;
 76         }
 77         return curPtr.data;
 78     }
 79     //获取链表的第一个元素
 80     public T getFirst(){
 81         return getByIdx(0);
 82     }
 83     //获取 链表的最后一个元素
 84     public T getLast(){
 85         return getByIdx(this.size-1);
 86     }
 87 
 88     //修改链表的第idx 元素为 newData
 89     public void setNewData(int idx,T newData){
 90         if(idx <0 && idx>=this.size)
 91             throw  new IllegalArgumentException("索引错误");
 92 
 93         Node curPtr = dummyHead.next;
 94         for (int i=0;i<idx;i++){
 95             curPtr = curPtr.next;
 96         }
 97         curPtr.data = newData;
 98     }
 99 
100     //查找 链表中是否存在 元素  data
101     public boolean contains(T data){
102         Node curPtr = dummyHead.next;
103         while (curPtr != null){
104             if(curPtr.data.equals(data))  //??????
105                 return true;
106             curPtr = curPtr.next;
107         }
108         return false;
109     }
110     
111     @Override
112     public String toString(){
113         StringBuilder builder = new StringBuilder();
114         builder.append(String.format("LinkList's Data is below: [size:%d] \n", this.size));
115 //        Node tempPtr = this.dummyHead.next;
116 
117 //        while (tempPtr !=null){
118 //            builder.append(tempPtr.data +"->");
119 //           tempPtr =tempPtr.next;
120 //        }
121         for (Node tempPtr = this.dummyHead.next;tempPtr != null;tempPtr = tempPtr.next)
122             builder.append(tempPtr.data +"->");
123 
124         builder.append("null");
125         return builder.toString();
126     }
127 }
View Code

从链表 中删除某个元素(by idx ,ele):

  1 package zcb.demo01;
  2 public class LinkedList <T> {   // 注意 链表这种数据结构 和二分搜索树不一样,它并不要求 元素具有可比性,
  3     private class Node{
  4         public T data;//具体数据
  5         public Node next; //引用(指针)
  6 
  7         //Node 节点的构造器
  8         public Node(T data,Node next){
  9             this.data  = data;
 10             this.next = next;
 11         }
 12         public Node(T data){
 13             this(data,null);
 14         }
 15         public  Node(){
 16             this(null,null);
 17         }
 18 
 19         @Override
 20         public String toString(){
 21             return this.data.toString();
 22         }
 23 
 24     }
 25 
 26     private Node dummyHead; //虚拟头节点
 27     private int size;
 28     //LinkedList 的构造器
 29 
 30     public LinkedList (){    //此时,当链表初始化的时候就已经存在一个 虚拟的头节点了。
 31         dummyHead = new Node(null,null);
 32         this.size = 0;
 33     }
 34     //获取链表中的元素个数
 35     public int getSize(){
 36         return this.size;
 37     }
 38     //返回链表是否为空
 39     public boolean isEmpty(){
 40         return this.size ==0;
 41     }
 42     //在链表 中间 索引(idx)  添加新元素
 43     public void insertByIdx(int idx,T newData){
 44         if(idx < 0 || idx > this.size){
 45             throw new IllegalArgumentException("idx 错误!");
 46         }
 47         Node tempPtr = new Node();
 48         tempPtr = this.dummyHead;
 49         for (int i =0;i< idx;i++){
 50             tempPtr = tempPtr.next;
 51         }
 52         Node node = new Node(newData,tempPtr.next);
 53         tempPtr.next = node;
 54 
 55         this.size ++;
 56     }
 57 
 58     //在链表头添加 新的元素 newData
 59     public void addFirst(T newData){
 60         insertByIdx(0,newData);
 61     }
 62     //在链表尾部 添加新元素
 63     public void addLast(T newData){
 64         this.insertByIdx(this.size,newData);
 65     }
 66 
 67 
 68     // 获取链表中的元素
 69     public T getByIdx(int idx){
 70         if(idx <0 && idx >= this.size)
 71             throw new IllegalArgumentException("idx 索引错误!!!");
 72 
 73         Node curPtr = dummyHead.next;
 74         for (int i=0;i<idx;i++){
 75             curPtr = curPtr.next;
 76         }
 77         return curPtr.data;
 78     }
 79     //获取链表的第一个元素
 80     public T getFirst(){
 81         return getByIdx(0);
 82     }
 83     //获取 链表的最后一个元素
 84     public T getLast(){
 85         return getByIdx(this.size-1);
 86     }
 87 
 88 
 89     //修改链表的第idx 元素为 newData
 90     public void setNewData(int idx,T newData){
 91         if(idx <0 && idx>=this.size)
 92             throw  new IllegalArgumentException("索引错误");
 93 
 94         Node curPtr = dummyHead.next;
 95         for (int i=0;i<idx;i++){
 96             curPtr = curPtr.next;
 97         }
 98         curPtr.data = newData;
 99     }
100 
101     //查找 链表中是否存在 元素  data
102     public boolean contains(T data){
103         Node curPtr = dummyHead.next;
104         while (curPtr != null){
105             if(curPtr.data.equals(data))  //??????
106                 return true;
107             curPtr = curPtr.next;
108         }
109         return false;
110     }
111 
112     //删除 指定的元素 (只删第一个)
113     public void removeByEle(T t){
114         Node tempPtr = this.dummyHead;
115         // 首先是要查找到 该元素的位置
116         while ( tempPtr.next != null){
117             if(tempPtr.next.data == t){
118                 // 找到了
119                 break;
120             }
121             tempPtr = tempPtr.next;
122         }
123         // 找到之后,就要删除它了,
124         if(tempPtr.next != null){
125             // 确实找了该元素
126             Node delNode = tempPtr.next; // 待删除的节点
127             tempPtr.next = delNode.next;
128 
129             delNode.next = null;
130             this.size --; //删完之后 要 --
131         }
132     }
133 
134     //删除 指定索引的元素  返回删除的元素
135     public T removeByIdx(int idx){
136         Node tempPtr = this.dummyHead;
137         for (int i=0;i<idx;i++){
138             tempPtr = tempPtr.next;
139         }
140         Node delNode = tempPtr.next;
141         tempPtr.next = delNode.next;
142         delNode.next = null; //手动释放
143         this.size --;
144 
145         return delNode.data;
146     }
147 
148     //删除第一个元素
149     public T removeFirst(){
150         return this.removeByIdx(0);
151     }
152     //删除最后一个元素
153     public T removeLast(){
154         return this.removeByIdx(this.size-1);
155     }
156 
157     @Override
158     public String toString(){
159         StringBuilder builder = new StringBuilder();
160         builder.append(String.format("LinkList's Data is below: [size:%d] \n", this.size));
161 //        Node tempPtr = this.dummyHead.next;
162 
163 //        while (tempPtr !=null){
164 //            builder.append(tempPtr.data +"->");
165 //           tempPtr =tempPtr.next;
166 //        }
167         for (Node tempPtr = this.dummyHead.next;tempPtr != null;tempPtr = tempPtr.next)
168             builder.append(tempPtr.data +"->");
169 
170         builder.append("null");
171         return builder.toString();
172     }
173 
174 }
LinkedList.java
 1 package cn.zcb.demo04;
 2 
 3 public class Test {
 4 
 5     public static void main(String[] args) {
 6         LinkedList<Integer> linkedList = new LinkedList<>();
 7 //        for (int i =0;i<10;i++){
 8 //            linkedList.addFirst(i);
 9 //        }
10 
11 
12         linkedList.addLast(12);
13         linkedList.addLast(11);
14         for (int i=0;i<6;i++){
15             linkedList.addLast(i);
16         }
17         System.out.println(linkedList);
18 
19 
20         linkedList.addFirst(15);
21         linkedList.addFirst(15);
22         linkedList.addFirst(15);
23         linkedList.addFirst(15);
24         System.out.println(linkedList);
25         linkedList.addLast(12);
26         linkedList.addLast(12);
27         linkedList.addLast(12);
28         linkedList.addLast(12);
29         linkedList.addLast(12);
30         System.out.println(linkedList);
31 
32         linkedList.removeByIdx(4);
33         System.out.println(linkedList);
34 
35         linkedList.removeFirst();
36         linkedList.removeFirst();
37 
38         System.out.println(linkedList);
39 
40         linkedList.removeLast();
41         linkedList.removeLast();
42         System.out.println(linkedList);
43     }
44 }
Test.java

 

目前代码的时间复杂度分析:

  1 package cn.zcb.demo04;
  2 
  3 public class LinkedList <T> {
  4 
  5     private class Node{
  6         public T data;//具体数据
  7         public Node next; //引用(指针)
  8 
  9         //Node 节点的构造器
 10         public Node(T data,Node next){
 11             this.data  = data;
 12             this.next = next;
 13         }
 14         public Node(T data){
 15             this(data,null);
 16         }
 17         public  Node(){
 18             this(null,null);
 19         }
 20 
 21         @Override
 22         public String toString(){
 23             return this.data.toString();
 24         }
 25 
 26     }
 27 
 28     private Node dummyHead; //虚拟头节点
 29     private int size;
 30     //LinkedList 的构造器
 31 
 32     public LinkedList (){    //此时,当链表初始化的时候就已经存在一个 虚拟的头节点了。
 33         dummyHead = new Node(null,null);
 34         this.size = 0;
 35     }
 36     //获取链表中的元素个数
 37     public int getSize(){
 38         return this.size;
 39     }
 40     //返回链表是否为空
 41     public boolean isEmpty(){
 42         return this.size ==0;
 43     }
 44     //在链表 中间 索引(idx)  添加新元素
 45     public void insertByIdx(int idx,T newData){
 46         if(idx < 0 || idx > this.size){
 47             throw new IllegalArgumentException("idx 错误!");
 48         }
 49         Node tempPtr = new Node();
 50         tempPtr = this.dummyHead;
 51         for (int i =0;i< idx;i++){
 52             tempPtr = tempPtr.next;
 53         }
 54         Node node = new Node(newData,tempPtr.next);
 55         tempPtr.next = node;
 56 
 57         this.size ++;
 58     }
 59 
 60     //在链表头添加 新的元素 newData
 61     public void addFirst(T newData){
 62         insertByIdx(0,newData);
 63     }
 64     //在链表尾部 添加新元素
 65     public void addLast(T newData){
 66         this.insertByIdx(this.size,newData);
 67     }
 68 
 69 
 70     // 获取链表中的元素
 71     public T getByIdx(int idx){
 72         if(idx <0 && idx >= this.size)
 73             throw new IllegalArgumentException("idx 索引错误!!!");
 74 
 75         Node curPtr = dummyHead.next;
 76         for (int i=0;i<idx;i++){
 77             curPtr = curPtr.next;
 78         }
 79         return curPtr.data;
 80     }
 81     //获取链表的第一个元素
 82     public T getFirst(){
 83         return getByIdx(0);
 84     }
 85     //获取 链表的最后一个元素
 86     public T getLast(){
 87         return getByIdx(this.size-1);
 88     }
 89 
 90 
 91     //修改链表的第idx 元素为 newData
 92     public void setNewData(int idx,T newData){
 93         if(idx <0 && idx>=this.size)
 94             throw  new IllegalArgumentException("索引错误");
 95 
 96         Node curPtr = dummyHead.next;
 97         for (int i=0;i<idx;i++){
 98             curPtr = curPtr.next;
 99         }
100         curPtr.data = newData;
101     }
102 
103     //查找 链表中是否存在 元素  data
104     public boolean contains(T data){
105         Node curPtr = dummyHead.next;
106         while (curPtr != null){
107             if(curPtr.data.equals(data))  //??????
108                 return true;
109             curPtr = curPtr.next;
110         }
111         return false;
112     }
113 
114 
115     //删除 指定索引的元素  返回删除的元素
116     public T removeByIdx(int idx){
117         Node tempPtr = this.dummyHead;
118         for (int i=0;i<idx;i++){
119             tempPtr = tempPtr.next;
120         }
121         Node delNode = tempPtr.next;
122         tempPtr.next = delNode.next;
123         delNode.next = null; //手动释放
124         this.size --;
125 
126         return delNode.data;
127     }
128 
129     //删除第一个元素
130     public T removeFirst(){
131         return this.removeByIdx(0);
132     }
133     //删除最后一个元素
134     public T removeLast(){
135         return this.removeByIdx(this.size-1);
136     }
137 
138     @Override
139     public String toString(){
140         StringBuilder builder = new StringBuilder();
141         builder.append(String.format("LinkList's Data is below: [size:%d] \n", this.size));
142 //        Node tempPtr = this.dummyHead.next;
143 
144 //        while (tempPtr !=null){
145 //            builder.append(tempPtr.data +"->");
146 //           tempPtr =tempPtr.next;
147 //        }
148         for (Node tempPtr = this.dummyHead.next;tempPtr != null;tempPtr = tempPtr.next)
149             builder.append(tempPtr.data +"->");
150 
151         builder.append("null");
152         return builder.toString();
153     }
154 
155 }
View Code

 

综合 O(n)

 

综合 O(n)

 

 

 

 

 

 

 

总结:

我们知道,只对链表头操作的话,时间时间复杂度是O(1)的。
所以,其实链表地方真正用途是:
对于增和删,它是只对链表头进行操作 O(1)。
对于查,它是只对链表头进行查O(1)。
它不进行修改

这样链表的时间复杂度也就能达到O(1) !!!

 

使用链表实现栈:


上面说过,我们应该只对链表头 进行增和删 和 查,这样可以使其时间复杂度为O(1)。

容易发现,满足这三个的条件的数据结构是

1 package cn.zcb.demo04;
2 
3 public interface MyInterface <T>{
4     public abstract void push(T a);
5     public abstract T pop();
6     public abstract T peek();  //top
7     public abstract int getSize();
8     public abstract boolean isEmpty();
9 }
MyInterface.java
  1 package cn.zcb.demo04;
  2 
  3 public class LinkedList <T> {
  4 
  5     private class Node{
  6         public T data;//具体数据
  7         public Node next; //引用(指针)
  8 
  9         //Node 节点的构造器
 10         public Node(T data,Node next){
 11             this.data  = data;
 12             this.next = next;
 13         }
 14         public Node(T data){
 15             this(data,null);
 16         }
 17         public  Node(){
 18             this(null,null);
 19         }
 20 
 21         @Override
 22         public String toString(){
 23             return this.data.toString();
 24         }
 25 
 26     }
 27 
 28     private Node dummyHead; //虚拟头节点
 29     private int size;
 30     //LinkedList 的构造器
 31 
 32     public LinkedList (){    //此时,当链表初始化的时候就已经存在一个 虚拟的头节点了。
 33         dummyHead = new Node(null,null);
 34         this.size = 0;
 35     }
 36     //获取链表中的元素个数
 37     public int getSize(){
 38         return this.size;
 39     }
 40     //返回链表是否为空
 41     public boolean isEmpty(){
 42         return this.size ==0;
 43     }
 44     //在链表 中间 索引(idx)  添加新元素
 45     public void insertByIdx(int idx,T newData){
 46         if(idx < 0 || idx > this.size){
 47             throw new IllegalArgumentException("idx 错误!");
 48         }
 49         Node tempPtr = new Node();
 50         tempPtr = this.dummyHead;
 51         for (int i =0;i< idx;i++){
 52             tempPtr = tempPtr.next;
 53         }
 54         Node node = new Node(newData,tempPtr.next);
 55         tempPtr.next = node;
 56 
 57         this.size ++;
 58     }
 59 
 60     //在链表头添加 新的元素 newData
 61     public void addFirst(T newData){
 62         insertByIdx(0,newData);
 63     }
 64     //在链表尾部 添加新元素
 65     public void addLast(T newData){
 66         this.insertByIdx(this.size,newData);
 67     }
 68 
 69 
 70     // 获取链表中的元素
 71     public T getByIdx(int idx){
 72         if(idx <0 && idx >= this.size)
 73             throw new IllegalArgumentException("idx 索引错误!!!");
 74 
 75         Node curPtr = dummyHead.next;
 76         for (int i=0;i<idx;i++){
 77             curPtr = curPtr.next;
 78         }
 79         return curPtr.data;
 80     }
 81     //获取链表的第一个元素
 82     public T getFirst(){
 83         return getByIdx(0);
 84     }
 85     //获取 链表的最后一个元素
 86     public T getLast(){
 87         return getByIdx(this.size-1);
 88     }
 89 
 90 
 91     //修改链表的第idx 元素为 newData
 92     public void setNewData(int idx,T newData){
 93         if(idx <0 && idx>=this.size)
 94             throw  new IllegalArgumentException("索引错误");
 95 
 96         Node curPtr = dummyHead.next;
 97         for (int i=0;i<idx;i++){
 98             curPtr = curPtr.next;
 99         }
100         curPtr.data = newData;
101     }
102 
103     //查找 链表中是否存在 元素  data
104     public boolean contains(T data){
105         Node curPtr = dummyHead.next;
106         while (curPtr != null){
107             if(curPtr.data.equals(data))  //??????
108                 return true;
109             curPtr = curPtr.next;
110         }
111         return false;
112     }
113 
114 
115     //删除 指定索引的元素  返回删除的元素
116     public T removeByIdx(int idx){
117         Node tempPtr = this.dummyHead;
118         for (int i=0;i<idx;i++){
119             tempPtr = tempPtr.next;
120         }
121         Node delNode = tempPtr.next;
122         tempPtr.next = delNode.next;
123         delNode.next = null; //手动释放
124         this.size --;
125 
126         return delNode.data;
127     }
128 
129     //删除第一个元素
130     public T removeFirst(){
131         return this.removeByIdx(0);
132     }
133     //删除最后一个元素
134     public T removeLast(){
135         return this.removeByIdx(this.size-1);
136     }
137 
138     @Override
139     public String toString(){
140         StringBuilder builder = new StringBuilder();
141         builder.append(String.format("LinkList's Data is below: [size:%d] \n", this.size));
142 //        Node tempPtr = this.dummyHead.next;
143 
144 //        while (tempPtr !=null){
145 //            builder.append(tempPtr.data +"->");
146 //           tempPtr =tempPtr.next;
147 //        }
148         for (Node tempPtr = this.dummyHead.next;tempPtr != null;tempPtr = tempPtr.next)
149             builder.append(tempPtr.data +"->");
150 
151         builder.append("null");
152         return builder.toString();
153     }
154 
155 }
LinkedList
 1 package cn.zcb.demo04;
 2 
 3 /*
 4 * MyStack 的底层是由链表来实现的。
 5 * */
 6 public class MyStack<T> implements MyInterface<T> {
 7     private LinkedList<T> linkedList; //链表 底层存储结构 。。
 8 
 9     //构造器
10     public MyStack(){
11         linkedList  = new LinkedList<>();
12     }
13 
14     //实现接口中 的方法
15     public  void push(T a){
16         linkedList.addFirst(a);
17     }
18     public  T pop(){
19         return linkedList.removeFirst();
20     }
21     public  T peek(){
22         return linkedList.getFirst();
23     }
24     public  int getSize(){
25         return linkedList.getSize();
26     }
27     public  boolean isEmpty(){
28         return linkedList.getSize() == 0;
29     }
30 
31     @Override
32     public String toString(){
33         String s = linkedList.toString().replace("LinkList","MyStack");
34         String ret = s.replace("->null","(栈底)");
35         return ret;
36         /* 另一种方法!
37         StringBuilder builder = new StringBuilder();
38         builder.append("MyStack's Data is below: \n");
39         builder.append(linkedList);
40         return builder.toString();
41          */
42     }
43 }
MyStack.java
 1 package cn.zcb.demo04;
 2 
 3 public class Test {
 4 
 5     public static void main(String[] args) {
 6         MyStack <Integer> stack = new MyStack<>();
 7         stack.push(1);
 8         stack.push(2);
 9         stack.push(3);
10         stack.push(4);
11         stack.push(5);
12 
13         System.out.println(stack);
14 
15         stack.pop();
16         System.out.println(stack);
17 
18         System.out.println(stack.getSize());
19 
20         System.out.println(stack.isEmpty());
21 
22         System.out.println(stack.peek());
23 
24     }
25 }
Test.java

 

 

数组栈 和 链表栈 进行对比!

 

它们之间的差距是差不多的。基本都是O(1) 的。

数组栈 的费时原因主要是,会不断的变长 数组的长度。
链表栈 的费时原因主要是,会new 大量的新节点,它会不停的寻找新的空间,可能会耗费一定的时间。
不过,运行时间的影响因素是多方面的,跟机器,Jvm等等都有关系。
但是,从理论上分析二者的时间复杂度,基本一致。

 

一般,二者时间出现2,3,倍的差异属于正常。
上百倍才是真的有差异。

 

使用链表实现队列:

队列,它是一种两头增删的操作。所以,如果用单纯的数组来时间它,势必有一端的时间复杂度为O(n) .
所以,我们前面是用循环队列来对其进行改进的,使其两头操作的时间复杂度都是 O(1)。
现在,如果用单纯的链表来实现队列,它也是一样。势必有一端的操作的时间复杂度为O(n).
所以,我们也要改进 上面我们实现的链表。

 

对于链表,之所以在其头部插入简单,是因为我们一直都有一个head 指针指着头。所以,如果想使得在尾部操作也变得简单,只需要再加上个tail指针,它始终指着尾部即可。

 

 

在两端插入节点 都是很容易的。

删除就不简单了。
如果head删除 简单,但是tail 删除就不简单了(它无法通过O(1)复杂度知道它前面的节点是谁)。

总结:

在head 端,添加和删除都容易。在tail端,只是添加元素容易。

所以,如果使用这样的链表来实现队列,我们就得用 tail 当做队尾。head当队头。

使用带有尾指针的 链表实现队列的   代码:

下面由于不涉及到从链表的中间操作元素,因此不涉及到处理逻辑统一问题,所以这里就不使用dummyHead(虚拟头节点)了。

此时,就要注意链表为空的情况了。

1 package cn.zcb.demo05;
2 public interface MyInterface_Queue <T> {
3     public abstract void enqueue(T a);  //入队
4     public abstract T dequeue();  //出队
5     public abstract T getFront();  //队头
6     public abstract int getSize();  //得到队列中的实际长度
7     public abstract boolean isEmpty();  //是否为空。
8 }
MyInterface_Queue.java
package cn.zcb.demo05;

/*
* 使用带有尾指针的 链表实现队列
* */
public class MyQueue<T> implements MyInterface_Queue<T> {
    private class Node{ //它就不用 再使用 泛型了
        public T data;
        public Node next;

        //Node 的构造器
        public Node(T data,Node next){
            this.data = data;
            this.next = next;
        }
        //构造器
        public Node(){
            this(null,null);
        }
        //构造器
        public Node(T data){
            this(data,null);
        }

        @Override
        public String toString(){
            return this.data.toString();
        }
    }

    private Node head,tail; //成员变量
    private int size;  //成员变量
    //构造器
    public MyQueue(){
        //初始状态
        this.head = null;
        this.tail = null;
        this.size = 0;
    }

    public  void enqueue(T a){
        //从 tail 入队 。
        if(this.tail == null){
            this.tail = new Node(a);
            this.head = this.tail;
        }else{
            this.tail.next = new Node(a);
            this.tail = this.tail.next; //一定不要忘了 维护新的tail
        }
        this.size++;
    }
    public  T dequeue(){
        if(this.isEmpty())
            throw new IllegalArgumentException("不能进行出队操作");
        Node delNode = this.head;
        this.head = this.head.next;
        delNode.next = null;
        if(head == null)  //可以设想 本来队列只有一个元素,出队一个之后就变为空队列了
            this.tail = null; //此时要维护一下tail

        this.size --;
        return delNode.data;
    }
    public  T getFront(){
        if (this.isEmpty())
            throw new IllegalArgumentException("队列不能为空");
        return this.head.data;
    }
    public  int getSize(){
        return this.size;
    }
    public  boolean isEmpty(){
        return this.size == 0;
    }

    @Override
    public String toString(){
        StringBuilder builder = new StringBuilder();
        builder.append(String.format("MyQueue's Data is below: [size:%d]\n(front)", this.getSize()));

        Node temp = this.head;
        while (temp != null){
            builder.append(temp +"-> ");
            temp = temp.next;
        }
        builder.append("(tail)");

        return builder.toString();
    }
}
MyQueue.java
 1 package cn.zcb.demo05;
 2 
 3 public class Test {
 4     public static void main(String[] args) {
 5         MyQueue<Integer> myQueue = new MyQueue<>();
 6             
 7         for (int i =0;i<10;i++){
 8             myQueue.enqueue(i);
 9             System.out.println(myQueue);
10         }
11         for (int i=0;i<3;i++){
12             myQueue.dequeue();
13             System.out.println(myQueue);
14 
15         }
16         
17     }
18     
19     
20     
21     
22     
23 }
Test.java

 

三种队列的时间比较:

 

可以看出,循环队列 和 链表队列 明显优于数组队列。

 

posted @ 2019-11-15 22:21  Zcb0812  阅读(248)  评论(0)    收藏  举报