Skip to content

Commit 7e3e66c

Browse files
committed
修改README以及完成remove方法
1 parent 5d48bfe commit 7e3e66c

3 files changed

Lines changed: 336 additions & 0 deletions

File tree

src/main/java/cn/byhieg/collectiontutorial/listtutorial/README.md

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 自动动手系列——实现List
2+
该包代码 主要实现了ArrayList与LinkedList。该README对应介绍实现的内容与过程。下面分别是ArrayList与LinkedList实现
3+
其ArrayList实现的内容和过程如下:
4+
15
# 自己动手系列——实现一个简单的ArrayList
26
ArrayList是Java集合框架中一个经典的实现类。他比起常用的数组而言,明显的优点在于,可以随意的添加和删除元素而不需考虑数组的大小。处于练手的目的,实现一个简单的ArrayList,并且把实现的过程在此记录。
37
实现的ArrayList主要的功能如下:
@@ -209,4 +213,281 @@ remove(Object o)和remove(int index)
209213
## 总结
210214
自此,一个简单的ArrayList就实现完了,实现的目的是为了弄清ArrayList动态数组的原理以及add与remove方法的内容实现。同时,也清楚了ArrayList最大的扩容空间就是Integer的最大值。该类的所有代码在[SimpleArrayList代码](https://github.com/byhieg/JavaTutorial/tree/master/src/main/java/cn/byhieg/collectiontutorial/listtutorial)
211215

216+
# 自己动手系列——实现一个简单的LinkedList
217+
218+
LinkedList与ArrayList都是List接口的具体实现类。LinkedList与ArrayList在功能上也是大体一致,但是因为两者具体的实现方式不一致,所以在进行一些相同操作的时候,其效率也是有差别的。
219+
对于抽象的数据结构——线性表而言,线性表分为两种,一种是顺序存储结构的顺序表,另一种是通过指针来描述其逻辑位置的链表。
220+
针对于具体的Java实现:
221+
222+
- 顺序存储的顺序表是用数组来实现的,以数组为基础进行封装各种操作而形成的List为ArrayList
223+
- 链表是用指针来描述其逻辑位置,在Java中以双向链表为基础进行封装各种操作而形成的List为LinkedList
224+
225+
针对插入与删除操作,ArrayList每插入一个元素,首先需要判断数组的空间够不够,不够要进行扩容,在有足够的空间的基础上,在指定的index位置上插入元素,但是该index及以后的元素都要后移。虽然删除操作不需要判断空间够不够,但同样需要该index及以后的元素向前移动,这些移动的操作会增加时间的复杂度。但是对于LinkedList就不一样,因为使用指针来指示其逻辑的位置,所以插入与删除的操作的时间复杂度都是 ** O(1) **
226+
227+
虽然对于ArrayList而言,插入与删除的时间复杂度很高,但是对于查找指定位置的元素这种操作而言,就非常的快,因为可以通过数组直接得到该下标对应的元素。反而,LinkedList而言,无法直接返回指定位置的元素,需要一个个查询,其时间的复杂度就是 ** O(n) **
228+
229+
与实现[ArrayList教程](http://www.cnblogs.com/qifengshi/p/6377614.html)一样,实现的目的主要在于练手以及掌握官方实现的原理和一些技巧,因此很多需要与其他类配合的方法和功能,就先不在这里实现如`iterator`
230+
231+
所以,实现的LinkedList的方法如下:
232+
233+
- add方法
234+
- get方法
235+
- indexOf方法
236+
- remove方法
237+
238+
与实现ArrayList的名字一样,为SimpleLinkedList。[源码地址](https://github.com/byhieg/JavaTutorial/blob/master/src/main/java/cn/byhieg/collectiontutorial/listtutorial/SimpleLinkedList.java),欢迎star,fork
239+
240+
241+
## 构建一个双向链表
242+
243+
构建的代码如下:
244+
245+
```
246+
247+
private static class Node<E>{
248+
E item;
249+
Node<E> next;
250+
Node<E> prev;
251+
252+
public Node(E item, Node<E> next, Node<E> prev) {
253+
this.item = item;
254+
this.next = next;
255+
this.prev = prev;
256+
}
257+
}
258+
259+
```
260+
261+
常规的双向链表的构建方法,一个数字域存放数组,一个前指针指向一个Node类型的元素,一个后指针指向一个Node类型的元素。
262+
263+
对于LinkedList的实现而言,还需要以下三个成员变量
264+
265+
```
266+
267+
private int size;
268+
269+
private Node<E> first;
270+
271+
private Node<E> last;
272+
273+
```
274+
275+
## Add方法
276+
这里实现的add方法是简单的add(E e)以及add(int index,E e)两个方法,addAll()将其他集合转换LinkedList的方法,暂时放到以后去实现。
277+
278+
add方法两个重载方法,其分别对应不同的添加方式。先说add(E e)方法的实现。
279+
280+
```
281+
282+
public boolean add(E element) {
283+
addAtLast(element);
284+
return true;
285+
}
286+
287+
```
288+
不指定位置添加元素,则默认添加到了链表的最后。addAtLast的核心代码如下:
289+
290+
```
291+
292+
private void addAtLast(E element) {
293+
Node<E> l = last;
294+
Node<E> node = new Node<E>(element, null, l);
295+
last = node;
296+
if (l == null) {
297+
first = node;
298+
} else {
299+
l.next = node;
300+
}
301+
size++;
302+
}
303+
304+
```
305+
306+
首先找到最后一位的Node元素,然后根据`element`创建一个新的Node元素,其next指向为null,prev指向为最后一位Node元素。在创建完Node元素之后,last就变成了先创建的Node元素,接下来只需要把新node元素加到链表中即可。即让l对象(原最后一位,现倒数第二位元素的next指针,指向新node元素)。至此,新node元素的next指向null,prev指向倒数第二个元素,倒数第二个元素的next指向新node,就将node成功加入链表。
307+
308+
上述的操作也可以看出,其插入的操作非常省时间,比起ArrayList,扩容,移动元素快很多。
309+
310+
add的第二个重载方法 add(int index ,E e),先看代码实现:
311+
312+
```
313+
314+
public void add(int index, E element) {
315+
checkRangeForAdd(index);
316+
if (index == size) {
317+
addAtLast(element);
318+
} else {
319+
Node<E> l = node(index);
320+
addBeforeNode(element, l);
321+
}
322+
}
323+
324+
```
325+
326+
首先判断要插入的index是否在范围内,在的话,再执行后续的add操作。如果要插入的index刚好是最后一位,则执行上面讲的addAtLast,如果不是,则得到index所对应的Node元素,执行addBeforeNode。
327+
获取index所对应的Node元素,是node方法,代码如下:
328+
329+
```
330+
331+
private Node<E> node(int index) {
332+
if (index < (size << 1)) {
333+
Node<E> cursor = first;
334+
for (int i = 0; i < index; i++) {
335+
cursor = cursor.next;
336+
}
337+
return cursor;
338+
} else {
339+
Node<E> cursor = last;
340+
for (int i = size - 1; i > index; i--) {
341+
cursor = cursor.prev;
342+
}
343+
return cursor;
344+
}
345+
}
346+
347+
```
348+
这里的查找采用二分查找,节省查找时间,而且也应用到了双向链表的特点。首先判断index在前一半的范围内,还是后一半的范围内。如果是前一半,则游标Node初始为first,用游标Node元素的next,不断指向index所在的元素。如果是后一半,则游标Node初始为last,用游标Node元素的prev,不断指向index所在的元素。
349+
350+
在指定元素的前面插入新节点的addBeforeNode的方法如下:
351+
352+
```
353+
354+
private void addBeforeNode(E element, Node<E> specifiedNode) {
355+
Node<E> preNode = specifiedNode.prev;
356+
Node<E> newNode = new Node<>(element, specifiedNode, preNode);
357+
if (preNode == null) {
358+
first = newNode;
359+
} else {
360+
preNode.next = newNode;
361+
}
362+
specifiedNode.prev = newNode;
363+
size++;
364+
}
365+
366+
```
367+
368+
插入的方式很简单,新节点的prev是原index元素的prev,新节点的next是原index元素。剩下的操作是把该node放到链表中,让原index元素的prev的next为新节点,但是要判断preNode是不是空,是的话,表示newNode为第一个元素,就是first。
369+
370+
至此,一个add方法,就实现完了。
371+
372+
## get方法
373+
374+
get方法在有了上述node方法之后,就非常的简单。代码如下:
375+
376+
```
377+
public E get(int index) {
378+
checkRange(index);
379+
return node(index).item;
380+
}
381+
382+
```
383+
384+
checkRange检查index是否不在范围内。
385+
386+
```
387+
private void checkRange(int index) {
388+
if (index >= size || index < 0) {
389+
throw new IndexOutOfBoundsException("指定index超过界限");
390+
}
391+
}
392+
393+
```
394+
395+
## indexOf方法
396+
397+
indexOf(Object o)用来得到指定元素的下标。
398+
399+
```
400+
401+
public int indexOf(Object element) {
402+
Node<E> cursor = first;
403+
int count = 0;
404+
while (cursor != null) {
405+
if (element != null) {
406+
if (element.equals(cursor.item)) {
407+
return count;
408+
}
409+
}else{
410+
if (cursor.item == null) {
411+
return count;
412+
}
413+
}
414+
count ++;
415+
cursor = cursor.next;
416+
}
417+
return -1;
418+
}
419+
420+
421+
```
422+
与ArrayList一样,从第一位开始查找,首先先判断element是不是null,分成两种情况。
423+
424+
## remove方法
425+
remove方法与add方法一样,同样有两个重载的方法,remove(Object o)与remove(int index)
426+
427+
先看简单的remove(int index)方法,代码如下:
428+
429+
```
430+
431+
public E remove(int index) {
432+
checkRange(index);
433+
return deleteLink(index);
434+
}
435+
436+
```
437+
438+
deleteLink是将该index所对应的节点的链接删除的方法,其代码如下:
439+
440+
```
441+
442+
private E deleteLink(int index) {
443+
Node<E> l = node(index);
444+
E item = l.item;
445+
Node<E> prevNode = l.prev;
446+
Node<E> nextNode = l.next;
447+
448+
if (prevNode == null) {
449+
first = nextNode;
450+
}else{
451+
prevNode.next = nextNode;
452+
l.next = null;
453+
}
454+
455+
if (nextNode == null) {
456+
last = prevNode;
457+
}else{
458+
nextNode.prev = prevNode;
459+
l.prev = null;
460+
}
461+
size--;
462+
l.item = null;
463+
return item;
464+
}
465+
466+
```
467+
468+
首先获得该index对应的Node元素,得到该Node元素的前一个元素和后一个元素。接下来,只需要将前一个元素和后一个元素直接相连即可,其他只需要额外判断前一个元素和后一个元素是否为null就行。在判断前一个元素是否为null的时候,只需要操作前一个元素,在判断后一个元素是否为null的时候,也只需要操作后一个元素。最后,将要删除的元素各个引用至为null。
469+
470+
remove另一个重载方法remove(Object o),在实现了indexOf和deleteLink方法之后,就非常简单。
471+
472+
```
473+
474+
public boolean remove(Object o) {
475+
int index = indexOf(o);
476+
if (index < 0){
477+
return false;
478+
}
479+
deleteLink(index);
480+
return true;
481+
}
482+
483+
```
484+
485+
获取该元素对应对应的下标,然后执行deleteLink方法,完成remove操作。
486+
487+
## 总结
488+
至此,一个功能简单的LinkedList就实现完成了,全部的代码可以看[源码地址](https://github.com/byhieg/JavaTutorial/blob/master/src/main/java/cn/byhieg/collectiontutorial/listtutorial/SimpleLinkedList.java),欢迎star,fork。
489+
490+
491+
492+
212493

src/main/java/cn/byhieg/collectiontutorial/listtutorial/SimpleLinkedList.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,49 @@ public int indexOf(Object element) {
107107
return -1;
108108
}
109109

110+
public E remove(int index) {
111+
checkRange(index);
112+
return deleteLink(index);
113+
}
114+
115+
public boolean remove(Object o) {
116+
int index = indexOf(o);
117+
if (index < 0){
118+
return false;
119+
}
120+
deleteLink(index);
121+
return true;
122+
}
123+
124+
private E deleteLink(int index) {
125+
Node<E> l = node(index);
126+
E item = l.item;
127+
Node<E> prevNode = l.prev;
128+
Node<E> nextNode = l.next;
129+
130+
if (prevNode == null) {
131+
first = nextNode;
132+
}else{
133+
prevNode.next = nextNode;
134+
l.next = null;
135+
}
136+
137+
if (nextNode == null) {
138+
last = prevNode;
139+
}else{
140+
nextNode.prev = prevNode;
141+
l.prev = null;
142+
}
143+
size--;
144+
l.item = null;
145+
return item;
146+
}
147+
148+
149+
150+
public int size(){
151+
return size;
152+
}
110153
private static class Node<E> {
111154
E item;
112155
Node<E> next;

src/test/java/cn/byhieg/collectiontutorialtest/SimpleLinkedListTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,16 @@ public void testIndexOf() throws Exception {
3131
System.out.println(lists.indexOf(1));
3232
}
3333

34+
public void testRemove() throws Exception {
35+
SimpleLinkedList<Integer> lists = new SimpleLinkedList<>();
36+
lists.add(111);
37+
lists.add(1231);
38+
lists.add(1513);
39+
for (int i = 0 ; i < lists.size();i++) {
40+
System.out.println(lists.get(i));
41+
}
42+
43+
lists.remove(1);
44+
System.out.println(lists.get(1));
45+
}
3446
}

0 commit comments

Comments
 (0)