LinkList(雙向鏈表實現)


LinkedList是用鏈表結構存儲數據的,比較適合數據的動態插入和刪除,隨機訪問和遍歷速度比較慢,還提供了List接口i中沒有定義的方法,專門用於操作表頭和表尾的元素,所以可以當作堆棧、隊列和雙向隊列來使用。LInkedList持有頭節點和尾節點的引用,有兩個構造器,一個是無參構造器,另一個是傳入外部集合構造器,它沒有像ArrayList一樣的初始大小的構造器。

  1 //集合元素個數
  2 
  3 transient int size = 0;
  4 
  5  
  6 
  7 //頭結點引用
  8 
  9 transient Node<E> first;
 10 
 11  
 12 
 13 //尾節點引用
 14 transient Node<E> last;
 15 
 16 //無參構造器
 17 public LinkedList() {} 
 18 
 19 //傳入外部集合的構造器
 20 public LinkedList(Collection<? extends E> c) {
 21     this();
 22     addAll(c);
 23 }
 24 
 25 //增(添加)
 26 public boolean add(E e) {
 27   
 28      //在鏈表尾部添加
 29     linkLast(e);
 30     return true;
 31 }
 32 
 33  
 34 //增(插入)
 35 public void add(int index, E element) {
 36 
 37     checkPositionIndex(index);
 38 
 39     if (index == size) {
 40         //在鏈表尾部添加
 41         linkLast(element);
 42     } else {
 43         //在鏈表中部插入
 44         linkBefore(element, node(index));
 45     }
 46 }
 47 
 48  
 49 
 50 //刪(給定下標)
 51 public E remove(int index) {
 52 
 53     //檢查下標是否合法
 54     checkElementIndex(index);
 55     return unlink(node(index));
 56 }
 57 
 58 //刪(給定元素)
 59 public boolean remove(Object o) {
 60     if (o == null) {
 61         for (Node<E> x = first; x != null; x = x.next) {
 62             if (x.item == null) {
 63                 unlink(x);
 64                 return true;
 65             }
 66         }
 67     } else {
 68         //遍歷鏈表
 69         for (Node<E> x = first; x != null; x = x.next) {
 70             if (o.equals(x.item)) {
 71                 //找到了就刪除
 72                 unlink(x);
 73                 return true;
 74             }
 75         }
 76     }
 77     return false;
 78 }
 79 
 80  
 81 
 82 //
 83 public E set(int index, E element) {
 84 
 85     //檢查下標是否合法
 86     checkElementIndex(index);
 87 
 88     //獲取指定下標的結點引用
 89     Node<E> x = node(index);
 90 
 91     //獲取指定下標結點的值
 92 
 93     E oldVal = x.item;
 94 
 95     //將結點元素設置為新的值
 96 
 97     x.item = element;
 98 
 99     //返回之前的值
100 
101     return oldVal;
102 
103 }
104 
105 
106 //
107 public E get(int index) {
108 
109     //檢查下標是否合法
110     checkElementIndex(index);
111 
112     //返回指定下標的結點的值
113     return node(index).item;
114 }

 

LinkedList的添加元素的方法主要是調用LinkLast和LinkBefore兩個方法,LinkLast方法是在鏈表后面鏈接一個元素,LinkBefore方法是在鏈表中間插入一個元素。LinkedList的刪除方法通過調用unlink方法將某個元素從鏈表中移除。下面是鏈表的插入和刪除操作的核心代碼。

 1 //鏈接到指定結點之前
 2 void linkBefore(E e, Node<E> succ) {
 3 
 4     //獲取給定結點的上一個結點引用
 5     final Node<E> pred = succ.prev;
 6 
 7     //創建新結點, 新結點的上一個結點引用指向給定結點的上一個結點
 8     //新結點的下一個結點的引用指向給定的結點
 9     final Node<E> newNode = new Node<>(pred, e, succ);
10 
11     //將給定結點的上一個結點引用指向新結點
12     succ.prev = newNode;
13 
14     //如果給定結點的上一個結點為空, 表明給定結點為頭結點
15     if (pred == null) {
16 
17         //將頭結點引用指向新結點
18         first = newNode;
19     } else {
20         //否則, 將給定結點的上一個結點的下一個結點引用指向新結點
21         pred.next = newNode;
22     }
23 
24     //集合元素個數加一
25     size++;
26 
27     //修改次數加一
28     modCount++;
29 
30 }
31 
32  
33 
34 //卸載指定結點
35 E unlink(Node<E> x) {
36 
37     //獲取給定結點的元素
38     final E element = x.item;
39 
40     //獲取給定結點的下一個結點的引用
41     final Node<E> next = x.next;
42 
43     //獲取給定結點的上一個結點的引用
44     final Node<E> prev = x.prev;
45 
46     //如果給定結點的上一個結點為空, 說明給定結點為頭結點
47     if (prev == null) {
48 
49         //將頭結點引用指向給定結點的下一個結點
50         first = next;
51     } else {
52         //將上一個結點的后繼結點引用指向給定結點的后繼結點
53         prev.next = next;
54         //將給定結點的上一個結點置空
55         x.prev = null;
56 
57     }
58 
59     //如果給定結點的下一個結點為空, 說明給定結點為尾結點
60     if (next == null) {
61 
62         //將尾結點引用指向給定結點的上一個結點
63         last = prev;
64     } else {
65 
66         //將下一個結點的前繼結點引用指向給定結點的前繼結點
67         next.prev = prev;
68         x.next = null;
69 
70     }
71 
72     //將給定結點的元素置空
73     x.item = null;
74 
75     //集合元素個數減一
76     size--;
77     //修改次數加一
78     modCount++;
79     return element;
80 
81 }
View Code

通過源碼的分析,對鏈表的插入和刪除的時間復雜度都是O(1),而對鏈表的查找和修改操作都需要遍歷鏈表進行元素的定位,這兩個操作都是調用的node(int index) 方法定位元素,如下代碼演示根據下標來定位元素。

 1 //根據指定位置獲取結點
 2 Node<E> node(int index) {
 3     //如果下標在鏈表前半部分, 就從頭開始查起
 4     if (index < (size >> 1)) {
 5         Node<E> x = first;
 6         for (int i = 0; i < index; i++) {
 7             x = x.next;
 8         }
 9         return x;
10     } else {
11         //如果下標在鏈表后半部分, 就從尾開始查起
12         Node<E> x = last;
13         for (int i = size - 1; i > index; i--) {
14             x = x.prev;
15         }
16         return x;
17     }
18 }
View Code

通過下標定位時先判斷是在鏈表的上半部還是下半部

上半部:從頭開始找;

下半部:從尾開始找;

因此通過下標的查找和修改操作的時間復雜度是O(n/2),通過對雙向鏈表的操作還可以實現單項隊列,雙向隊列和棧的功能。

單向隊列的操作的代碼:

 1 //獲取頭結點
 2 public E peek() {
 3     final Node<E> f = first;
 4     return (f == null) ? null : f.item;
 5 }
 6  
 7 //獲取頭結點
 8 public E element() {
 9     return getFirst();
10 }
11  
12 //彈出頭結點
13 public E poll() {
14     final Node<E> f = first;
15     return (f == null) ? null : unlinkFirst(f);
16 }
17  
18 //移除頭結點
19 public E remove() {
20     return removeFirst();
21 }
22  
23 //在隊列尾部添加結點
24 public boolean offer(E e) {
25     return add(e);
26 }
View Code

雙向隊列的操作:

 1 //在頭部添加
 2 public boolean offerFirst(E e) {
 3     addFirst(e);
 4     return true;
 5 }
 6  
 7 //在尾部添加
 8 public boolean offerLast(E e) {
 9     addLast(e);
10     return true;
11 }
12  
13 //獲取頭結點
14 public E peekFirst() {
15     final Node<E> f = first;
16     return (f == null) ? null : f.item;
17  }
18  
19 //獲取尾結點
20 public E peekLast() {
21     final Node<E> l = last;
22     return (l == null) ? null : l.item;
23 }
View Code

棧操作:

1 //入棧
2 public void push(E e) {
3     addFirst(e);
4 }
5  
6 //出棧
7 public E pop() {
8     return removeFirst();
9 }
View Code

對LindedList,有:

1. LinkedList是基於雙向鏈表實現的,不論是增刪改查方法還是隊列和棧的實現,都可通過操作結點實現

2. LinkedList無需提前指定容量,因為基於鏈表操作,集合的容量隨着元素的加入自動增加

3. LinkedList刪除元素后集合占用的內存自動縮小,無需像ArrayList一樣調用trimToSize()方法

4. LinkedList的所有方法沒有進行同步,因此它也不是線程安全的,應該避免在多線程環境下使用

5. 以上分析基於JDK1.7,其他版本會有些出入,因此不能一概而論

以上內容部分借鑒於:https://blog.csdn.net/iteen/article/details/83109717


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM