寫在前面:
鏈表采用一組地址任意的存儲單元存放線性表中的數據元素,鏈式結構的線性表不會按線性的邏輯順序來保存數據元素,它需要在每一個元素里保存一個引用下一個數據元素的引用(或者叫指針)。它的每個節點都必須包含數據元素本身和一或兩個用來引用上一個/下一個節點的引用。
優點:由於不必須按順序存儲,鏈表在插入、刪除數據元素時比順序線性表快得多。使用鏈表結構可以克服順序線性表(基於數組)需要預先知道數據大小的缺點,鏈表可以充分利用計算機內存空間,實現靈活的內存動態管理。
缺點:鏈表在查找一個節點或者訪問特點編號的節點則比順序線性表慢得多。由於鏈表結構失去了數組隨機存取的優點,同時鏈表由於增加了節點的指針域,空間開銷比較大。
以下使用Java語言實現一個單鏈表:
1 package com.ietree.basic.datastructure.linklist; 2 3 /** 4 * 鏈式存儲結構 5 * 6 * @param <T> 7 * @author Dylan 8 */ 9 public class LinkList<T> { 10 11 // 定義一個內部類Node,Node實例代表鏈表的節點 12 private class Node { 13 14 // 保存節點的數據 15 private T data; 16 // 指向下一個節點的引用 17 private Node next; 18 // 無參構造器 19 public Node() { 20 } 21 // 初始化全部屬性的構造器 22 public Node(T data, Node next) { 23 this.data = data; 24 this.next = next; 25 } 26 } 27 28 // 保存該鏈表的頭節點 29 private Node header; 30 // 保存該鏈表的尾節點 31 private Node tail; 32 // 保存該鏈表中已包含的節點數 33 private int size; 34 35 // 創建空鏈表 36 public LinkList() { 37 // 空鏈表,header和tail都是null 38 header = null; 39 tail = null; 40 } 41 42 // 以指定數據元素來創建鏈表,該鏈表只有一個元素 43 public LinkList(T element) { 44 45 header = new Node(element, null); 46 tail = header; 47 size++; 48 49 } 50 51 // 返回鏈表的長度 52 public int length() { 53 54 return size; 55 56 } 57 58 // 獲取鏈式線性表中索引為index處的元素 59 public T get(int index) { 60 61 return getNodeByIndex(index).data; 62 63 } 64 65 // 根據索引index獲取指定位置的節點 66 public Node getNodeByIndex(int index) { 67 68 if (index < 0 || index > size - 1) { 69 70 throw new IndexOutOfBoundsException("線性表索引越界"); 71 72 } 73 74 // 從header節點開始 75 Node current = header; 76 for (int i = 0; i < size && current != null; i++, current = current.next) { 77 if (i == index) { 78 return current; 79 } 80 } 81 82 return null; 83 } 84 85 // 查找鏈式線性表中指定元素的索引 86 public int locate(T element) { 87 88 // 從頭節點開始搜索 89 Node current = header; 90 for (int i = 0; i < size && current != null; i++, current = current.next) { 91 if (current.data.equals(element)) { 92 return i; 93 } 94 } 95 return -1; 96 } 97 98 // 向線性表的指定位置插入一個元素 99 public void insert(T element, int index) { 100 101 if (index < 0 || index > size) { 102 103 throw new IndexOutOfBoundsException("線性表索引越界"); 104 105 } 106 107 // 如果還是空鏈表 108 if (header == null) { 109 110 add(element); 111 112 } else { 113 // 當index為0時,即在鏈表頭處插入 114 if (index == 0) { 115 116 addAtHeader(element); 117 118 } else { 119 120 // 獲取插入點的前一個節點 121 Node prev = getNodeByIndex(index - 1); 122 // 讓prev的next指向新節點,讓新節點的next引用指向原來prev的下一個節點 123 prev.next = new Node(element, prev.next); 124 size++; 125 126 } 127 } 128 } 129 130 // 采用尾插法為鏈表添加新節點 131 public void add(T element) { 132 133 // 如果該鏈表還是空鏈表 134 if (header == null) { 135 136 header = new Node(element, null); 137 // 只有一個節點,header、tail都指向該節點 138 tail = header; 139 140 } else { 141 142 // 創建新節點 143 Node newNode = new Node(element, null); 144 // 讓尾節點的next指向新增的節點 145 tail.next = newNode; 146 // 以新增節點作為新的尾節點 147 tail = newNode; 148 149 } 150 size++; 151 } 152 153 // 采用頭插法為鏈表添加新節點 154 public void addAtHeader(T element) { 155 156 // 創建新節點,讓新節點的next指向原來的header 157 // 並以新節點作為新的header 158 header = new Node(element, header); 159 // 如果插入之前是空鏈表 160 if (tail == null) { 161 162 tail = header; 163 164 } 165 size++; 166 } 167 168 // 刪除鏈式線性表中指定索引處的元素 169 public T delete(int index) { 170 171 if (index < 0 || index > size - 1) { 172 173 throw new IndexOutOfBoundsException("線性表索引越界"); 174 175 } 176 Node del = null; 177 // 如果被刪除的是header節點 178 if (index == 0) { 179 180 del = header; 181 header = header.next; 182 183 } else { 184 185 // 獲取刪除點的前一個節點 186 Node prev = getNodeByIndex(index - 1); 187 // 獲取將要被刪除的節點 188 del = prev.next; 189 // 讓被刪除節點的next指向被刪除節點的下一個節點 190 prev.next = del.next; 191 // 將被刪除節點的next引用賦為null 192 del.next = null; 193 194 } 195 size--; 196 return del.data; 197 } 198 199 // 刪除鏈式線性表中最后一個元素 200 public T remove() { 201 202 return delete(size - 1); 203 204 } 205 206 // 判斷鏈式線性表是否為空表 207 public boolean empty() { 208 209 return size == 0; 210 211 } 212 213 // 清空線性表 214 public void clear() { 215 216 // 將header、tail賦為null 217 header = null; 218 tail = null; 219 size = 0; 220 221 } 222 223 public String toString() { 224 225 // 鏈表為空鏈表時 226 if (empty()) { 227 228 return "[]"; 229 230 } else { 231 232 StringBuilder sb = new StringBuilder("["); 233 for (Node current = header; current != null; current = current.next) { 234 sb.append(current.data.toString() + ", "); 235 } 236 int len = sb.length(); 237 return sb.delete(len - 2, len).append("]").toString(); 238 239 } 240 } 241 }
測試類:
1 package com.ietree.basic.datastructure.linklist; 2 3 public class LinkListTest { 4 public static void main(String[] args) { 5 6 LinkList<String> list = new LinkList<String>(); 7 list.insert("aaaa", 0); 8 list.add("bbbb"); 9 list.add("cccc"); 10 list.insert("dddd", 1); 11 System.out.println(list); 12 list.delete(2); 13 System.out.println(list); 14 System.out.println("cccc在鏈表中的位置:" + list.locate("cccc")); 15 System.out.println("鏈表中索引2處的元素:" + list.get(2)); 16 } 17 }
程序輸出:
[aaaa, dddd, bbbb, cccc] [aaaa, dddd, cccc] cccc在鏈表中的位置:2 鏈表中索引2處的元素:cccc