寫在前面:
雙向鏈表是一種對稱結構,它克服了單鏈表上指針單向性的缺點,其中每一個節點即可向前引用,也可向后引用,這樣可以更方便的插入、刪除數據元素。
由於雙向鏈表需要同時維護兩個方向的指針,因此添加節點、刪除節點時指針維護成本更大;但雙向鏈表具有兩個方向的指針,因此可以向兩個方向搜索節點,因此雙向鏈表在搜索節點、刪除指定索引處節點時具有較好的性能。
Java語言實現雙向鏈表:
1 package com.ietree.basic.datastructure.dublinklist; 2 3 /** 4 * 雙向鏈表 5 * 6 * @author Dylan 7 */ 8 public class DuLinkList<T> { 9 10 // 定義一個內部類Node,Node實例代表鏈表的節點 11 private class Node { 12 13 // 保存節點的數據 14 private T data; 15 // 保存上個節點的引用 16 private Node prev; 17 // 指向下一個節點的引用 18 private Node next; 19 20 // 無參構造器 21 public Node() { 22 } 23 24 // 初始化全部屬性的構造器 25 public Node(T data, Node prev, Node next) { 26 27 this.data = data; 28 this.prev = prev; 29 this.next = next; 30 31 } 32 33 } 34 35 // 保存該鏈表的頭節點 36 private Node header; 37 // 保存該鏈表的尾節點 38 private Node tail; 39 // 保存該鏈表中已包含的節點數 40 private int size; 41 42 // 創建空鏈表 43 public DuLinkList() { 44 45 // 空鏈表,header和tail都是null 46 header = null; 47 tail = null; 48 49 } 50 51 // 以指定數據元素來創建鏈表,該鏈表只有一個元素 52 public DuLinkList(T element) { 53 54 header = new Node(element, null, null); 55 // 只有一個節點,header、tail都指向該節點 56 tail = header; 57 size++; 58 59 } 60 61 // 返回鏈表的長度 62 public int length() { 63 64 return size; 65 66 } 67 68 // 獲取鏈式線性表中索引為index處的元素 69 public T get(int index) { 70 71 return getNodeByIndex(index).data; 72 73 } 74 75 // 根據索引index獲取指定位置的節點 76 public Node getNodeByIndex(int index) { 77 78 if (index < 0 || index > size - 1) { 79 80 throw new IndexOutOfBoundsException("線性表索引越界"); 81 82 } 83 if (index <= size / 2) { 84 85 // 從header節點開始 86 Node current = header; 87 for (int i = 0; i <= size / 2 && current != null; i++, current = current.next) { 88 if (i == index) { 89 90 return current; 91 92 } 93 } 94 95 } else { 96 97 // 從tail節點開始搜索 98 Node current = tail; 99 for (int i = size - 1; i > size / 2 && current != null; i++, current = current.prev) { 100 if (i == index) { 101 102 return current; 103 104 } 105 } 106 107 } 108 109 return null; 110 } 111 112 // 查找鏈式線性表中指定元素的索引 113 public int locate(T element) { 114 115 // 從頭結點開始搜索 116 Node current = header; 117 for (int i = 0; i < size && current != null; i++, current = current.next) { 118 119 if (current.data.equals(element)) { 120 return i; 121 } 122 123 } 124 return -1; 125 126 } 127 128 // 向線性鏈表的指定位置插入一個元素 129 public void insert(T element, int index) { 130 131 if (index < 0 || index > size) { 132 throw new IndexOutOfBoundsException("線性表索引越界"); 133 } 134 135 // 如果還是空鏈表 136 if (header == null) { 137 138 add(element); 139 140 } else { 141 142 // 當index為0時,也就是在鏈表頭處插入 143 if (index == 0) { 144 145 addAtHeader(element); 146 147 } else { 148 149 // 獲取插入點的前一個節點 150 Node prev = getNodeByIndex(index - 1); 151 // 獲取插入點的節點 152 Node next = prev.next; 153 // 讓新節點的next引用指向next節點,prev引用指向prev節點 154 Node newNode = new Node(element, prev, next); 155 // 讓prev的next節點指向新節點 156 prev.next = newNode; 157 // 讓prev的下一個節點的prev指向新節點 158 next.prev = newNode; 159 size++; 160 } 161 162 } 163 164 } 165 166 // 采用尾插法為鏈表添加新節點 167 public void add(T element) { 168 169 // 如果該鏈表還是空鏈表 170 if (header == null) { 171 172 header = new Node(element, null, null); 173 // 只有一個節點,header、tail都指向該節點 174 tail = header; 175 176 } else { 177 178 // 創建新節點,新節點的pre指向原tail節點 179 Node newNode = new Node(element, tail, null); 180 // 讓尾節點的next指向新增的節點 181 tail.next = newNode; 182 // 以新節點作為新的尾節點 183 tail = newNode; 184 185 } 186 size++; 187 } 188 189 // 采用頭插法為鏈表添加新節點 190 public void addAtHeader(T element) { 191 // 創建新節點,讓新節點的next指向原來的header 192 // 並以新節點作為新的header 193 header = new Node(element, null, header); 194 // 如果插入之前是空鏈表 195 if (tail == null) { 196 197 tail = header; 198 199 } 200 size++; 201 } 202 203 // 刪除鏈式線性表中指定索引處的元素 204 public T delete(int index) { 205 206 if (index < 0 || index > size - 1) { 207 208 throw new IndexOutOfBoundsException("線性表索引越界"); 209 210 } 211 Node del = null; 212 // 如果被刪除的是header節點 213 if (index == 0) { 214 215 del = header; 216 header = header.next; 217 // 釋放新的header節點的prev引用 218 header.prev = null; 219 220 } else { 221 222 // 獲取刪除節點的前一個節點 223 Node prev = getNodeByIndex(index - 1); 224 // 獲取將要被刪除的節點 225 del = prev.next; 226 // 讓被刪除節點的next指向被刪除節點的下一個節點 227 prev.next = del.next; 228 // 讓被刪除節點的下一個節點的prev指向prev節點 229 if (del.next != null) { 230 231 del.next.prev = prev; 232 233 } 234 235 // 將被刪除節點的prev、next引用賦為null 236 del.prev = null; 237 del.next = null; 238 239 } 240 size--; 241 return del.data; 242 } 243 244 // 刪除鏈式線性表中最后一個元素 245 public T remove() { 246 247 return delete(size - 1); 248 249 } 250 251 // 判斷鏈式線性表是否為空表 252 public boolean empty() { 253 254 return size == 0; 255 256 } 257 258 // 清空線性表 259 public void clear() { 260 261 // 將底層數組所有元素賦為null 262 header = null; 263 tail = null; 264 size = 0; 265 266 } 267 268 public String toString() { 269 270 // 鏈表為空鏈表 271 if (empty()) { 272 273 return "[]"; 274 275 } else { 276 277 StringBuilder sb = new StringBuilder("["); 278 for (Node current = header; current != null; current = current.next) { 279 280 sb.append(current.data.toString() + ", "); 281 282 } 283 int len = sb.length(); 284 return sb.delete(len - 2, len).append("]").toString(); 285 286 } 287 288 } 289 290 // 倒序toString 291 public String reverseToString() { 292 293 if (empty()) { 294 295 return "[]"; 296 297 } else { 298 299 StringBuilder sb = new StringBuilder("["); 300 for (Node current = tail; current != null; current = current.prev) { 301 302 sb.append(current.data.toString() + ", "); 303 304 } 305 int len = sb.length(); 306 return sb.delete(len - 2, len).append("]").toString(); 307 308 } 309 310 } 311 312 }
測試類:
1 package com.ietree.basic.datastructure.dublinklist; 2 3 /** 4 * 測試類 5 * 6 * @author Dylan 7 */ 8 public class DuLinkListTest { 9 10 public static void main(String[] args) { 11 12 DuLinkList<String> list = new DuLinkList<String>(); 13 list.insert("aaaa", 0); 14 list.add("bbbb"); 15 list.insert("cccc", 0); 16 // 在索引為1處插入一個新元素 17 list.insert("dddd", 1); 18 // 輸出順序線性表的元素 19 System.out.println(list); 20 // 刪除索引為2處的元素 21 list.delete(2); 22 System.out.println(list); 23 System.out.println(list.reverseToString()); 24 // 獲取cccc字符串在順序線性表中的位置 25 System.out.println("cccc在順序線性表中的位置:" + list.locate("cccc")); 26 System.out.println("鏈表中索引1處的元素:" + list.get(1)); 27 list.remove(); 28 System.out.println("調用remove方法后的鏈表:" + list); 29 list.delete(0); 30 System.out.println("調用delete(0)后的鏈表:" + list); 31 32 } 33 34 }
程序輸出:
[cccc, dddd, aaaa, bbbb] [cccc, dddd, bbbb] [bbbb, dddd, cccc] cccc在順序線性表中的位置:0 鏈表中索引1處的元素:dddd 調用remove方法后的鏈表:[cccc, dddd] 調用delete(0)后的鏈表:[dddd]