双向链表主要在节点的构成上与单向链表略有区别,双向链表的节点拥有一个元素存储空间和两个指针域,一个指针指向下一个节点,可记为next,一个指针指向上一个节点,可记为prev。如果节点是链表的头节点,则该节点的prev为null,如果节点是尾节点,该节点的next为null。当链表中只有一个节点的时候,头节点和尾节点指向链表中的唯一节点,如下图所示。
从应用角度上来说,双向链表比单向链表的使用场景更多,操作起来也更为方便,我们常用双向链表来实现队列和栈的操作。这也是本文中,我们需要为双向链表添加的额外功能。
双向链表,我们依然以实现Collection为基础,这一部分我们不再进行说明,我们主要对双向链表添加的特殊方法进行说明分析。
1.public E pushFrist(E e):向链表的头结点添加新节点。
2.public E pushLast(E e):向链表的结尾处添加新节点。
3.public E popFrist():从链表的头结点取出元素,并从链表中删除该元素。
4.public E popLast():从链表的尾节点取出元素,并从链表中删除该元素。
以上这四个方法可以通过组合,达到栈和队列的效果。
5.public Iterator reIterator():获取一个反向迭代器。覆盖Collection集合的迭代器是一个正向迭代器。由于双向链表的结构特点,我们也可以从尾节点向头节点进行遍历。那么根据这个特点,在双向链表中提供一个反向迭代器用于遍历。
双向链表和单向链表在实现Collection接口的时候,有很多方法是通用的,我们几乎是可以拿来直接使用的,但下面列举的几方法,我们需要重新实现:
7.public boolean add(E e)
8.public boolean remove(Object o)
9.public void clear()
上述三个方法之所以要重写设计的原因有两个:1.单向链表和双向链表的结构有些许改变,在删除和添加的时候,涉及到了更多数据的变化。2.单向链表在删除节点的功能上留下了隐患。在双向链表中,我们在删除节点以及清空链表的时候,会采用更保险的方式进行删除。
此外getHead和getLast两个非Collection接口方法,我们也可以沿用之前的写法。
在双向链表中,我们可以将正向迭代器和反向迭代器合并实现。在创建迭代器对象的时候,我们通过构造方法提供几个额外的参数:
1.Node start,表示迭代的起始节点。
2.boolean isNext,初始值为true,表示迭代器的迭代方式,为true表示说明该迭代器是正向迭代器,迭代的时候用指针域的next进行迭代。否则为反向迭代器,迭代的时候用指针域的prev进行迭代。
在iterator()和reIterator()方法中我们可以通过参数来创建正反两种迭代器。
基于add方法的添加元素,默认在链表的尾部添加节点,当添加完毕后,要更新尾节点和新节点信息,如下图所示。
add的添加方式也可以被pushLast方法所调用,它们都是从链表的尾部添加新节点。另一个添加方法pushFrist是从链表的头部添加节点,这个方法在实现的时候要注意更新头结点的指针域的信息。需要注意pushLast和pushFrist在添加成功的时候,需要将添加的新元素返回。
在双向链表中删除节点,是影响最大的操作,当一个节点被删除的时候,要更新前一节点和后一节点的指针域信息,如下图所示。
删除的时候为了提高安全性,被删除的节点还应该清空指针域与元素存储区的数据。在双向链表的方法中,此外popLast和popFrist方法都会从链表中的两端删除节点。
此外,考虑到在删除节点的时候,有可能影响头节点和尾节点的信息,所以我们将节点删除单独封装成一个私有方法removeNode(Node node),这样便于其它删除方法的调用。
双向链表命名为DoubleLink泛型类,并实现Collection接口。DoubleLink的内部成员变量与单向链表一致,不做变化。DoubleLink有两个内部类,分别是LinkIterator和Node,双向链表中的Node类比单向链表中多一个成员变量Node prev,用于表示上一个节点指针域,该变量需要在Node构造方法中进行初始化赋值。
其次,为了能够自定义链表长度,我们需要额外提供一个构造方法,用于初始化链表的最大长度MAX_SIZE的值。
LinkIterator具体声明格式为"class LinkIterator implements Iterator"。包含的成员变量和方法如下:
private boolean isNext:是否正向迭代,默认值为true,构造对象时可重新赋值。
private Node start:遍历起始节点,通过构造方法赋值。如果提供的参数isNext为true,则start.next引用头节点,反之last.prev引用尾节点。
1. public boolean hasNext():如果isNext为true,返回start.next!=null,如果为false,返回start.prev!=null。
2. punblic T next():如果isNext为true,start节点后移,否则start前移。返回移动后节点的元素内容。
1.public DoubleLink(int linkSize):linkSize为链表最大长度的临界值,如果小于0,则默认的最大长度为2<<10。
2.public boolean add(T e):
3.private void removeNode(Node node)
4.public void clear()
5.public boolean remove(Object o)
6.public E pushFrist(E e)
7.public E pushLast(E e)
8. public E popFrist()
9. public E popLast()
10. public Iterator iterator():返回new LinkIterator(head, true)。
11. public Iterator reIterator():返回new LinkIterator(last, false)。
其它为提到的方法,我们都可以沿用单向链表的实现思路来实现。
01. import java.util.Collection;
02. import java.util.Iterator;
03.
04. public class DoubleLink<E> implements Collection<E>{
05.
06. private int size=0;
07. private Node<E> head=null;
08. private Node<E> last=null;
09. private final int MAX_SIZE;
10.
11. public DoubleLink() {
12. MAX_SIZE=2<<10;
13. }
14.
15. public DoubleLink(int linkSize) {
16. MAX_SIZE=linkSize>0?linkSize:2<<10;
17. }
18.
19. static class Node<E>{
20. E item;
21. Node<E> next;
22. Node<E> prev;
23.
24. public Node(E item,Node<E> prev,Node<E> next){
25. this.item=item;
26. this.prev=prev;
27. this.next=next;
28. }
29. }
30.
31. class LinkIterator implements Iterator<E>{
32.
33. private Node<E> start=null;
34. private boolean isNext=true;
35.
36. public LinkIterator(Node<E> startNode,boolean isNext) {
37. this.isNext=isNext;
38. start=this.isNext?new Node<>(null, null, startNode)
39. :new Node<>(null, startNode, null);
40. }
41.
42. @Override
43. public boolean hasNext() {
44. if(isNext) {
45. return start.next!=null;
46. }else {
47. return start.prev!=null;
48. }
49. }
50.
51. @Override
52. public E next() {
53. start=isNext?start.next:start.prev;
54. return start.item;
55. }
56.
57. }
58.
59. private void removeNode(Node<E> node) {
60. if(node==null) return;
61. //获取被删除节点的前后节点。
62. Node<E> prevNode=node.prev;
63. Node<E> nextNode=node.next;
64. //如果前节点不为空,更新前节点的next为后节点。
65. if(prevNode==null) {
66. head=head.next;
67. }else {
68. prevNode.next=nextNode;
69. }
70.
71. if(nextNode==null) {
72. last=last.prev;
73. }else {
74. nextNode.prev=prevNode;
75. }
76. //清空被删除节点的所有引用
77. node.next=null;
78. node.prev=null;
79. node.item=null;
80. size--;
81. }
82.
83.
84.
85. @Override
86. public int size() {
87. return this.size;
88. }
89.
90. @Override
91. public boolean isEmpty() {
92. return size==0?true:false;
93. }
94.
95. @Override
96. public boolean contains(Object o) {
97. if(o==null) return false;
98. Node<E> node=head;
99. while(node!=null) {
100. if(node.item.equals(o)) {
101. return true;
102. }
103. node=node.next;
104. }
105.
106. return false;
107. }
108.
109. @Override
110. public Iterator<E> iterator() {
111.
112. LinkIterator it=new LinkIterator(head, true);
113.
114. return it;
115. }
116.
117. public Iterator<E> reIterator() {
118. LinkIterator it=new LinkIterator(last, false);
119. return it;
120. }
121.
122. @Override
123. public boolean add(E e) {
124. if(e==null || size>=MAX_SIZE ) return false;
125. Node<E> node=new Node<E>(e, null, null);
126. if(size==0) {
127. head=last=node;
128. }else {
129. node.prev=last;
130. last.next=node;
131. last=node;
132. }
133. size++;
134. return true;
135. }
136.
137. @Override
138. public boolean remove(Object o) {
139.
140. if(o==null) return false;
141. boolean isRemove=false;
142. Node<E> node=head;
143. while(node!=null) {
144. if(node.item.equals(o)) {
145. Node<E> removeNode=node;
146. node=node.next;
147. removeNode(removeNode);
148. isRemove=true;
149. }else {
150. node=node.next;
151. }
152. }
153. return isRemove;
154. }
155.
156. @Override
157. public boolean containsAll(Collection<?> c) {
158. if(c==null) return false;
159.
160. Iterator<?> it=c.iterator();
161. while(it.hasNext()) {
162. Object e=it.next();
163. if(!contains(e)) {
164. return false;
165. }
166. }
167.
168. return true;
169. }
170.
171. @Override
172. public boolean addAll(Collection<? extends E> c) {
173. if(c==null) return false;
174. if(size>MAX_SIZE-c.size()) return false;
175. Iterator<? extends E> it=c.iterator();
176. while(it.hasNext()) {
177. if(!add(it.next())) {
178. return false;
179. }
180. }
181. return true;
182. }
183.
184. @Override
185. public boolean removeAll(Collection<?> c) {
186. if(c==null) return false;
187. Iterator<?> it=c.iterator();
188. while(it.hasNext()) {
189. Object collectEle=it.next();
190. if(contains(collectEle)) {
191. remove(collectEle);
192. }
193. }
194. return true;
195. }
196.
197. @Override
198. public boolean retainAll(Collection<?> c) {
199. if(c==null) return false;
200. Node<E> node=head;
201. while(node!=null) {
202. if(!c.contains(node.item)) {
203. remove(node.item);
204. }
205. node=node.next;
206. }
207.
208. return true;
209. }
210.
211. @Override
212. public void clear() {
213. while(head!=null) {
214. removeNode(head);
215. }
216. head=last=null;
217. size=0;
218. }
219.
220. @Override
221. public Object[] toArray() {
222. if(size==0) return null;
223. Object[] array=new Object[size];
224. Iterator<E> it=this.iterator();
225. for(int i=0;i<size;i++) {
226. array[i]=it.next();
227. }
228.
229. return array;
230. }
231.
232. @SuppressWarnings("unchecked")
233. @Override
234. public <T> T[] toArray(T[] a) {
235. T[] array=a;
236. Object[] src=this.toArray();
237. for(int i=0;i<array.length;i++) {
238. if(i<src.length) {
239. array[i]=(T) src[i];
240. }else {
241. array[i]=null;
242. }
243. }
244.
245. return array;
246. }
247.
248. public String toString() {
249. StringBuilder strs=new StringBuilder("");
250. Node<E> start=head;
251. while(start!=null) {
252. strs.append(start.item);
253. start=start.next;
254. strs.append(start!=null?",":"");
255. }
256.
257. return strs.toString();
258. }
259.
260. public E getHead() {
261. return head==null?null:head.item;
262. }
263.
264. public E getLast() {
265. return last==null?null:last.item;
266. }
267.
268. public E pushFrist(E e) {
269.
270. if(e==null) return null;
271.
272. if(size==0) return pushLast(e);
273.
274. Node<E> node=new Node<>(e,null,head);
275. head.prev=node;
276. head=node;
277. size++;
278.
279. return e;
280. }
281.
282. public E pushLast(E e) {
283. if(e==null) return null;
284.
285. return add(e)?e:null;
286. }
287.
288. public E popFrist() {
289. if(size==0) return null;
290.
291. E e=head.item;
292. removeNode(head);
293.
294. return e;
295. }
296.
297. public E popLast() {
298.
299. if(size==0) return null;
300. E e=last.item;
301. removeNode(last);
302.
303. return e;
304. }
305. }
两个测试用的数据初始方法和数据输出方法分别如下所示:
方法1 初始化链表数据,并返回链表。
01. public static DoubleLink<Integer> createLink(int size){
02. DoubleLink<Integer> link=new DoubleLink<>();
03. for(int i=0;i<size;i++) {
04. link.add(i);
05. }
06.
07. return link;
08. }
方法2 输出链表重要信息,并输出链表内容
01. public static void showLink(Link<?> link) {
02. System.out.println("head="+link.getHead()
03. +",last="+link.getLast()
04. +",size="+link.size());
05. System.out.println("link="+link);
06. }
1. iterator和reIterator方法测试用例内容:测试正向迭代和反向迭代是否正常使用。
01. public static void iteratorTest() {
02. DoubleLink<Integer> dl=createLink(15);
03. Iterator<Integer> it=dl.iterator();
04. while(it.hasNext()) {
05. System.out.print(it.next()+" ");
06. }
07. System.out.println();
08. Iterator<Integer> it2=dl.reIterator();
09. while(it2.hasNext()) {
10. System.out.print(it2.next()+" ");
11. }
用例运行结果:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
2.remove方法测试用例内容:删除头结点、尾节点,中间节点,观察删除前后的链表变化。
01. public static void removeTest() {
02. DoubleLink<Integer> dl=createLink(15);
03. dl.add(14);
04. dl.add(15);
05. showLink(dl);
06. System.out.println("remove 15:"+dl.remove(new Integer(15)));
07. showLink(dl);
08. System.out.println("remove 0:"+dl.remove(new Integer(0)));
09. showLink(dl);
10. System.out.println("remove 14:"+dl.remove(new Integer(14)));
11. showLink(dl);
12. }
用例运行结果:
head=0,last=15,size=17
link=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,14,15
remove 15:true
head=0,last=14,size=16
link=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,14
remove 0:true
head=1,last=14,size=15
link=1,2,3,4,5,6,7,8,9,10,11,12,13,14,14
remove 14:true
head=1,last=13,size=13
link=1,2,3,4,5,6,7,8,9,10,11,12,13
3. pushFrist和pushLast方法测试用例内容:在空链表基础上用push方式添加数据,每种添加方式各添加10个,最后观察链表的变化。
01. public static void pushTest() {
02. DoubleLink<Integer> dl=new DoubleLink<>(100);
03. for(int i=0;i<20;i++) {
04. if(i<10) {
05. dl.pushFrist(i);
06. }else {
07. dl.pushLast(i);
08. }
09. }
10. showLink(dl);
11. }
用例运行结果:
head=9,last=19,size=20
link=9,8,7,6,5,4,3,2,1,0,10,11,12,13,14,15,16,17,18,19
4. popFrist和popLast方法测试用例内容:从有数据的链表中用pop方式获取数据,并观察链表的变化。
01. public static void popTest() {
02. DoubleLink<Integer> dl=createLink(15);
03. showLink(dl);
04. System.out.println("head pop="+dl.popFrist());
05. showLink(dl);
06. System.out.println("last pop="+dl.popLast());
07. showLink(dl);
08. }
用例运行结果:
head=0,last=14,size=15
link=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14
head pop=0
head=1,last=14,size=14
link=1,2,3,4,5,6,7,8,9,10,11,12,13,14
last pop=14
head=1,last=13,size=13
link=1,2,3,4,5,6,7,8,9,10,11,12,13
5. clear方法测试用例内容:观察链表清空前后的信息变化。
01. public static void clearTest() {
02. DoubleLink<Integer> dl=createLink(15);
03. showLink(dl);
04. dl.clear();
05. showLink(dl);
06. }
用例运行结果:
head=0,last=14,size=15
link=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14
head=null,last=null,size=0
link=
双向链表是使用比较多的一种数据结构,我们可以参考Collection集合体系下的LinkedList类的实现,它是一个比较好的链表实现,如果我们要对链表的性能和功能有所提升,参考它的源码时非常有意义的。
我们设计的链表并不是一个线程安全的链表,在多线程的情况下,它依然会出现各种各样的问题,有兴趣的话可以为链表加上同步功能。关于链表的设计和实现我们就告一段落。