定義:鏈表是一種遞歸的數據結構,它或者為空(null),或者是指向一個結點(node)的引用,該結點含有一個泛型的元素和一個指向另一條鏈表的引用。
要構造鏈表,首先要定義結點(Node):
private class Node
{
Item item;
Node next;
}
一個 Node 對象含有兩個實例變量,類型分別為 Item(類型參數)和 Node。
現在,根據遞歸定義,只需要一個 Node 類型的變量就能表示一條鏈表,只要保證它的值是 null 或者指向另一個 Node 對象且該對象的 next 域指向了另一條鏈表即可。
例如,下面構造了一條含有元素 to、be 和 or 的鏈表:
public class Test<Item>
{
private class Node
{
Item item;
Node next;
}
public static void main(String[] args)
{
Node first = new Node();
Node second = new Node();
Node third = new Node();
first.item = "to";
second.item = "be";
third.item = "or";
first.next = second;
second.next = third;
}
}
上述代碼的結果是,third 是一條鏈表(它是一個結點的引用,該結點指向 null,即一個空鏈表), second 也是一條鏈表(它是一個結點的引用,且該結點含有一個指向 third 的引用,而 third 是一條鏈表), first 也是一條鏈表(它是一個結點的引用,且該結點含有一個指向 second 的引用,而 second 是一條鏈表)。
鏈表表示的是一列元素。也可以用一個數組來表示一列元素,但在鏈表中向序列插入元素或是從序列中刪除元素更方便。
在表頭插入結點
要在首結點為 first 的給定鏈表開頭插入字符串 not,需要先將 first 保存在 oldfirst 中,然后用 first 指向一個新結點,並將它的 item 域設為 not,next 域設為 oldfirst。如圖 1 所示。
![]() |
從表頭刪除結點
刪除一條鏈表的首結點,只需將 first 指向 first. next。如圖 2 所示。
![]() |
在表尾插入結點
如何才能在鏈表的尾部添加一個新結點?要完成這個任務,需要一個指向鏈表最后一個結點的鏈接,添加結點時,該結點的鏈接被修改並指向一個含有新元素的新結點。
鏈接表示對結點的引用。
在鏈表結尾插入新結點的過程如圖 3 所示。
![]() |
需要謹慎考慮是否需要維護一個額外的鏈接,因為每個修改鏈表的操作都需要添加檢查是否要修改該變量(以及作出相應修改)的代碼。例如,剛剛討論過的刪除鏈表首結點的代碼就可能改變指向鏈表的尾結點的引用——當鏈表中只有一個結點時,它既是首結點又是尾結點!另外,圖 3 中的代碼也無法處理鏈表為空的情況(oldlast.next 會產生空指針異常)。類似這些情況的細節使編寫鏈表代碼時需要格外細心。
其他位置的插入和刪除操作
前面已經展示了在鏈表中如何通過若干指令實現以下操作,其中可以通過 first 鏈接訪問鏈表的首結點並通過 last 鏈接訪問鏈表的尾結點:
❏ 在表頭插入結點;
❏ 從表頭刪除結點;
❏ 在表尾插入結點。
其他操作,例如以下幾種,就不那么容易實現了:
❏ 刪除指定的結點;
❏ 在指定結點前插入一個新結點。
例如,怎樣才能刪除鏈表的尾結點呢?last 鏈接幫不上忙,因為需要將鏈表尾結點的前一個結點中的鏈接(它指向的正是 last)值改為 null。在缺少其他信息的情況下,唯一的解決辦法就是遍歷整條鏈表並找出指向 last 的結點。這種解決方案並不理想,因為它所需的時間和鏈表的長度成正比。
實現任意插入和刪除操作的標准解決方案是使用雙向鏈表,其中每個結點都含有兩個鏈接,分別指向不同的方向。
遍歷
可以用以下循環處理鏈表中的每個結點,其中 first 指向鏈表的首結點:
for (Node x = first; x ! = null; x = x.next)
{
// 處理 x.item
}
數組與鏈表
數組與鏈表是兩種常見的表示對象集合的方式,兩者非常基礎,常常被稱為順序存儲和鏈式存儲。下面是(Java)數組和鏈表各自的優缺點:
![]() |
總結自《算法(第四版)》1.3 背包、隊列和棧