想了解更多數據結構以及算法題,可以關注微信公衆號“數據結構和算法”,每天一題爲你精彩解答。
基礎知識
鏈表是一種物理存儲單元上非連續的一種數據結構,看名字我們就知道他是一種鏈式的結構,就像一羣人手牽着手一樣。鏈表有單向的,雙向的,還有環形的。
1,單向鏈表
我們先定義一個最簡單的單向鏈表結點類
1 class Node<E> {
2 E data;
3 Node<E> next;
4
5 Node(E data, Node<E> next) {
6 this.data = data;
7 this.next = next;
8 }
9 }
這個類非常簡單,他只有兩個變量,一個是data值,一個是指向下一個結點的指針。我們先來看一下單向的鏈表是什麼樣的,
鏈表頭是不存儲任何數據的,他只有指向下一個結點的指針,當然如果我們不需要頭結點,直接拿第一個結點當做頭結點也是可以的,像下面這樣
單向鏈表的增刪
鏈表不像數組那樣,可以通過索引來獲取,單向鏈表查找的時候必須從頭開始往後一個個找,而不能從中間找,也不能從後往前找。我們來看一下鏈表的添加
1,添加到尾結點
添加到尾結點比較簡單,我們只需要找到之前鏈表的尾結點,然後讓他的next指針指向新的結點即可。
2,添加到中間結點
添加到中間結點,分爲兩步,比如我們要在ab結點之間添加新結點n,第一步讓新結點n的指針指向b,然後再讓a的指針指向新結點n即可。
3,刪除鏈表的尾結點
只需要讓尾結點的上一個結點的指針指向null即可。
4,刪除鏈表的中間結點
只需要把要刪除結點的前一個結點的指針指向要刪除結點的下一個結點即可,最好還要把要刪除結點的數據清空,並且讓他的指針指向null。
2,單向環形鏈表
環形鏈表一般分爲兩種,一種是單向環形鏈表,一種是雙向環形鏈表。我們首先來看一下單向的環形鏈表是什麼樣的
他和單向非環形鏈表的區就是,單向非環形鏈表的尾結點的指針是指向null的,而環形的是指向頭結點。關於他的增刪和單向非環形的差不多,只不過在尾結點的時候會有點區別,我們主要來看下這兩種
1,添加到尾結點
2,刪除尾結點
3,雙向鏈表
我們來定義一個雙向鏈表結點的類
1 class Node<E> {
2 E data;
3 Node<E> next;
4 Node<E> prev;
5
6 Node(Node<E> prev, E data, Node<E> next) {
7 this.data = data;
8 this.next = next;
9 this.prev = prev;
10 }
11 }
雙向鏈表不光有指向下一個結點的指針,而且還有指向上一個結點的指針,他比單向鏈表多了一個指向前一個結點的指針,單向鏈表我們只能從前往後找,而雙向鏈表我們不光可以從前往後找,而且還可以從後往前找。我們來看一下雙向鏈表是什麼樣的。
雙向鏈表我們可以從頭到尾查找,也可以從尾到頭查找,雙向鏈表在代碼中最常見的就是LinkedList了(java語言),雙向鏈表的增刪和單向鏈表的增刪很類似,只不過雙向鏈表不光要調整他指向的下一個結點,還要調整他指向的上一個結點。這裏我們來結合圖形和代碼的方式來分析一下雙向鏈表的增刪。
1,添加到尾結點
我們結合着代碼來看下
1 void linkLast(Object e) {
2 final Node<Object> last = tail;
3 final Node<Object> newNode = new Node<>(last, e, null);
4 tail = newNode;
5 if (last == null)
6 head = newNode;
7 else
8 last.next = newNode;
9 size++;
10 }
總共分爲4步
2,添加到頭結點
1 private void linkFirst(Object e) {
2 //用臨時變量firt保存head結點
3 final Node<Object> first = head;
4 //創建一個新的結點,讓他的pre指針指向null,next指針指向head結點
5 final Node<Object> newNode = new Node<>(null, e, first);
6 //讓head指向新的結點
7 head = newNode;
8 //如果之前的first爲空,說明之前鏈表是空的,讓head和tial同時指向新結點即可
9 if (first == null)
10 tail = newNode;
11 else//如果之前鏈表不爲空,讓之前first結點的pre指向新的結點
12 first.prev = newNode;
13 size++;
14 }
添加到頭結點和添加到尾結點很類似,圖就不在畫了,大家可以看下上面的代碼。
3,添加到指定結點之前
比如我們在a結點之前添加一個指定的結點,先來看下代碼
1 void linkBefore(Object e, Node<Object> a) {
2 final Node<Object> pred = a.prev;
3 final Node<Object> newNode = new Node<>(pred, e, a);
4 a.prev = newNode;
5 if (pred == null)
6 head = newNode;
7 else
8 pred.next = newNode;
9 size++;
10 }
假如我們在a結點之前添加一個結點,圖就不在細畫,簡單看一下即可
4,刪除鏈表的尾結點
1private Object unlinkLast(Node<Object> last) {
2 final Object element = last.data;
3 final Node<Object> prev = last.prev;
4 last.data = null;
5 last.prev = null;
6 tail = prev;
7 if (prev == null)//如果只有一個結點,把尾結點刪除,相當於把鏈表清空了
8 head = null;
9 else
10 prev.next = null;
11 size--;
12 return element;
13}
如果鏈表只有一個結點的話,我們把它刪除,相當於直接把鏈表清空了,這種很好理解,就不再畫。下面畫一個長度大於1的鏈表,然後刪除最後一個結點
5,刪除鏈表的頭結點
1 private Object unlinkFirst(Node<Object> first) {
2 final Object element = first.data;
3 final Node<Object> next = first.next;
4 first.data = null;
5 first.next = null;
6 head = next;
7 if (next == null)
8 tail = null;
9 else
10 next.prev = null;
11 size--;
12 return element;
13 }
6,刪除鏈表的中間結點
1 Object unlink(Node<Object> x) {
2 final Object element = x.data;
3 final Node<Object> next = x.next;//x的前一個結點
4 final Node<Object> prev = x.prev;//x的後一個結點
5
6 if (prev == null) {//前一個結點是空
7 head = next;
8 } else {
9 prev.next = next;
10 x.prev = null;
11 }
12
13 if (next == null) {//後一個結點是空
14 tail = prev;
15 } else {
16 next.prev = prev;
17 x.next = null;
18 }
19
20 x.data = null;
21 size--;
22 return element;
23 }
4,雙向環形鏈表
雙向環形鏈表在代碼中最常見的就是LinkedHashMap了,這個一般用於圖片緩存的比較多一些,LRUCache這個類裏面主要使用的就是LinkedHashMap(java語言中),通過上面的分析,如果對linkedList能理解的話,那麼雙向環形鏈表也就不難理解了,其實原理都差不多,這裏就不在過多介紹,下面是雙向環形鏈表的圖。