Java实现双向链表

1 双向链表需求

        双向链表主要在节点的构成上与单向链表略有区别,双向链表的节点拥有一个元素存储空间和两个指针域,一个指针指向下一个节点,可记为next,一个指针指向上一个节点,可记为prev。如果节点是链表的头节点,则该节点的prev为null,如果节点是尾节点,该节点的next为null。当链表中只有一个节点的时候,头节点和尾节点指向链表中的唯一节点,如下图所示。

双向链表
        从应用角度上来说,双向链表比单向链表的使用场景更多,操作起来也更为方便,我们常用双向链表来实现队列和栈的操作。这也是本文中,我们需要为双向链表添加的额外功能。

2 功能及算法分析

        双向链表,我们依然以实现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接口方法,我们也可以沿用之前的写法。

2.1 迭代功能实现分析

        在双向链表中,我们可以将正向迭代器和反向迭代器合并实现。在创建迭代器对象的时候,我们通过构造方法提供几个额外的参数:

        1.Node start,表示迭代的起始节点。
        2.boolean isNext,初始值为true,表示迭代器的迭代方式,为true表示说明该迭代器是正向迭代器,迭代的时候用指针域的next进行迭代。否则为反向迭代器,迭代的时候用指针域的prev进行迭代。

        在iterator()和reIterator()方法中我们可以通过参数来创建正反两种迭代器。

2.2 添加元素

        基于add方法的添加元素,默认在链表的尾部添加节点,当添加完毕后,要更新尾节点和新节点信息,如下图所示。
双向链表添加节点
        add的添加方式也可以被pushLast方法所调用,它们都是从链表的尾部添加新节点。另一个添加方法pushFrist是从链表的头部添加节点,这个方法在实现的时候要注意更新头结点的指针域的信息。需要注意pushLast和pushFrist在添加成功的时候,需要将添加的新元素返回。

2.3 删除节点

        在双向链表中删除节点,是影响最大的操作,当一个节点被删除的时候,要更新前一节点和后一节点的指针域信息,如下图所示。
双向链表删除节点
        删除的时候为了提高安全性,被删除的节点还应该清空指针域与元素存储区的数据。在双向链表的方法中,此外popLast和popFrist方法都会从链表中的两端删除节点。

        此外,考虑到在删除节点的时候,有可能影响头节点和尾节点的信息,所以我们将节点删除单独封装成一个私有方法removeNode(Node node),这样便于其它删除方法的调用。

3 详细设计说明

        双向链表命名为DoubleLink泛型类,并实现Collection接口。DoubleLink的内部成员变量与单向链表一致,不做变化。DoubleLink有两个内部类,分别是LinkIterator和Node,双向链表中的Node类比单向链表中多一个成员变量Node prev,用于表示上一个节点指针域,该变量需要在Node构造方法中进行初始化赋值。

        其次,为了能够自定义链表长度,我们需要额外提供一个构造方法,用于初始化链表的最大长度MAX_SIZE的值。

3.1 LinkIterator内部类

        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前移。返回移动后节点的元素内容。

3.2 DoubleLink部分方法的实现

        1.public DoubleLink(int linkSize):linkSize为链表最大长度的临界值,如果小于0,则默认的最大长度为2<<10。

        2.public boolean add(T e):

  • 方法描述:向链表中添加新元素e。返回值为true表明添加成功,失败则为false。
  • 实现过程:1.先判断size>=MAX_SIZE 条件是否成立,如果成立说明超出链表长度,方法返回false,如果e为null也返回false。2.创建新节点node,其次再判断链表中是否是空链表,如果是空链表,更新头结点和尾节点信息为node。如果不是空链表,在尾节点后添加node节点,并更新尾节点和新节点的指针域信息。3.添加成功后,size自增一个单位,方法返回true。

       3.private void removeNode(Node node)

  • 方法描述:删除指定节点node,并更新节点前后的指针域信息。该方法是一个私有方法,会在其它方法中被多次调用。其它方法在删除节点时,不用再操作size的值。
  • 实现过程: 1.如果node为null,方法结束。2.获取node的前后节点prevNode、nextNode。删除node节点(清空所有信息),如果prevNode为null,说明node是头节点,更新头节点信息。如果nextNode为null,说明node为尾节点,更新尾节点信息。3.清空node节点成功后,size自减。

       4.public void clear()

  • 方法描述:清空链表信息。与单向链表设计不同,在双向链表中,我们用迭代的方式删除每一个节点,并将每一个节点的信息清除。
  • 实现过程: 1.以head节点不为Null为遍历条件,使用removeNode方法移除头节点。2.遍历结束后,将head,last节点赋值空引用,size归0。

       5.public boolean remove(Object o)

  • 方法描述:删除链表中所有指定的元素o。
  • 实现过程: 1.如果目标元素o为null,方法返回false。2.创建遍历节点node,初始值为head。3.以node!=null为条件进行遍历,在遍历的内部中,如果遍历的节点与目标o相等,删除该节点(无论是否与目标o相等,判断完毕后,node节点后移)。

       6.public E pushFrist(E e)

  • 方法描述:向链表的头部放入节点。
  • 实现过程: 1.如果目标元素e为null,方法返回false。2.如果链表长度为0,调用并返回pushLast方法。3.创建新节点node,更新node和head节点指针域信息,size自增。

       7.public E pushLast(E e)

  • 方法描述:向链表的尾部放入节点。
  • 实现过程: 1.如果目标元素e为null,方法返回false。2.调用add方法,如果add方法返回true,返回e,否则返回null。

       8. public E popFrist()

  • 方法描述:弹出链表头部元素,并删除该节点。
  • 实现过程: 1.如果链表长度为0,方法返回false。2.获取头部元素e,使用removeNode方法删除头结点head。3.返回e。

       9. public E popLast()

  • 方法描述:弹出链表尾部元素,并删除该节点。
  • 实现过程: 1.如果链表长度为0,方法返回false。2.获取尾部元素e,使用removeNode方法删除尾结点last。3.返回e。

       10. public Iterator iterator():返回new LinkIterator(head, true)。
       11. public Iterator reIterator():返回new LinkIterator(last, false)。

       其它为提到的方法,我们都可以沿用单向链表的实现思路来实现。

3.3 代码实现

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.	}

4 链表的测试用例

       两个测试用的数据初始方法和数据输出方法分别如下所示:

       方法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=

5 总结

       双向链表是使用比较多的一种数据结构,我们可以参考Collection集合体系下的LinkedList类的实现,它是一个比较好的链表实现,如果我们要对链表的性能和功能有所提升,参考它的源码时非常有意义的。

       我们设计的链表并不是一个线程安全的链表,在多线程的情况下,它依然会出现各种各样的问题,有兴趣的话可以为链表加上同步功能。关于链表的设计和实现我们就告一段落。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值