鏈表的概念
我們知道數組是很常用的數據儲存方式,而鏈表就是繼數組之后,第二種最通用的數據儲存方式了。數組需要存放在連續的空間,計算機很容易實現。而鏈表的好處是不用確定空間長度,不夠的時候,直接申請新的節點,幫助插入。所以鏈表可以更靈活地進行內存分配。
鏈表(linked list)是一種序列形的數據結構,其中包含了很多通過鏈接 (link) 被串起來的節點。每個節點有一個數據域,儲存着節點的數值,還有一個指針域,指向下一個節點。

簡單來說,鏈表是由一系列節點組成的,每個節點通過鏈接和其他節點鏈接。每個節點包含兩個屬性,一個是數值,記錄了節點的數據,另一個是指針,記錄了下一個節點的位置。正因為節點的指針能夠記錄其他節點的位置,所以我們不需要將節點按順序排列。
鏈表的類型
鏈表有很多不同的類型,但本質上都是一系列通過鏈接相連的節點。
單鏈表:鏈表中最簡單的一種是單向鏈表,它包含兩個域,一個信息域和一個指針域。這個鏈接指向列表中的下一個節點,而最后一個節點則指向一個空值。

雙鏈表:雙鏈表是為了解決鏈表節點不知道前面節點的尷尬,於是在節點的定義中,存在一個父節點,和一個子節點。這樣就能順着父節點往回找。

循環鏈表:在一個 循環鏈表中, 首節點和末節點被連接在一起。這種方式在單向和雙向鏈表中皆可實現。

塊狀鏈表:塊狀鏈表結合了數組和鏈表的特性,將連續成段的數組通過鏈接串起來。塊狀鏈表的特點是插入很靈活,尋找特定元素也比正常鏈表快速。
鏈表是一種重要的基礎數據結構,可以用來生成其它類型的數據結構,比如之后講到的堆、棧、樹和圖等等。單向鏈表也是鏈表中最基礎的類型,其它變種也是基於它的,在接下來的一部分,我會着重講解單向鏈表。
鏈表的基本操作
每種數據結構都有其對應的操作,而鏈表包含以下基本操作:
- 插入:將一個新元素插入鏈表的任意位置。
- 刪除:將一個元素從鏈表中刪除。
- 查找(遍歷):查找一個特定的元素。
- 更新:更新一個節點上的元素。
單向鏈表的實現
以下是鏈表的定義:
public class LinkedList { static class ListNode( int val; ListNode next; public ListNode(int val) { this.val = val; } ListNode head; ListNode tail; int size; public LinkedList() { head = null; tail = null; size = 0; } }
其中ListNode是節點的定義,其中的屬性val就是數據,而next就是下一個節點。而 LinkedList 則是單向鏈表類,其中包含頭節點head和尾節點tail。在初始化的時候,我們將頭尾節點都設為null,size初始化為0。
插入
在一個鏈表中插入新元素分為以下三種情況:
- 插入到鏈表的最前頭,作為新的頭結點。
- 插入到鏈表中間的位置。
- 插入到鏈表的尾部,作為鏈表中最后的元素。
雖然新元素的插入位置不固定,但是鏈表插入的思想的固定的。插入只需要兩步:將新節點的next指針指向插入位置后的節點,再將插入節點前的next指針指向新插入的節點。
比如,我們在鏈表 {1, 2, 3, 4} 的基礎上分別實現在頭部、中間、尾部插入新元素5,過程如下圖所示:

要注意的是,我們必須先執行步驟1,再執行步驟2;如果先執行步驟2,否則會導致插入位置后續的節點無法被找到。以下是代碼:
// insert the element to the specific position, position starts from index 0 public void insert(int position, int number) { if (position > size) { return; } ListNode newNode = new ListNode(number); if (position == 0) { newNode.next = head; head = newNode; if(tail == null) { tail = newNode; } size++; } else if (position == size) { this.append(number); } else { ListNode prev = head; for (int i = 0; i < position - 1; i++) { prev = prev.next; } ListNode next = prev.next; newNode.next = next; prev.next = newNode; size++; } } // append the new element to the end of the list public void append(int number) { ListNode newNode = new ListNode(number); if(tail == null) { tail = newNode; } else { tail.next = newNode; tail = newNode; } size++; }
刪除
刪除元素如下圖所示,只要找到我們要刪除的節點,然后將前面節點的next指針指向被刪除節點的下一節點即可:

public void delete(int number) { if(head != null && head.val == number) { // delete the head node head = head.next; size--; if(size == 0) { // corner case: no element is left tail = head; } } else { ListNode prev = head; ListNode cur = head; while (prev != null && cur != null) { if (cur.val == number) { if(cur == tail) { // corner case: delete the last element tail = prev; } prev.next = cur.next; size--; return; } prev = cur; cur = cur.next; } } }
查找
查找元素比較簡單,只要從頭節點開始遍歷,找到與目標值相對應的節點,返回其位置即可:
public int search(int number) { ListNode cur = head; for(int index = 0; cur != null; index++) { if(cur.val == number) { return index; } cur = cur.next; } return -1; }
更新
更新鏈表和查找相似,只要找到對應的節點后,改變節點的值即可:
public int update(int oldValue, int newValue) { ListNode cur = head; for(int index = 0; cur != null; index++) { if(cur.val == oldValue) { cur.val = newValue; return index; } cur = cur.next; } return -1; }
LeetCode題目
- Design Linked List (707)
- Merge Two Sorted Lists (21)
- Reverse Linked List (206)
- Linked List Cycle (141)
- Linked List Cycle II (142)
- Plus One Linked List (369)
- LRU Cache (146)