用Java學資料結構 - 雙向鏈結串列 Doubly Linked List
介紹與概念
雙向鏈結串列的定義
雙向鏈結串列 (Doubly Linked List) 是一種進階的鏈結串列,由節點 (Node) 組成,每個節點包含資料 (data)、指向下一個節點的指標 (next) 和指向前一個節點的指標 (prev)。透過這些指標,可以輕鬆地在正向和反向之間遍歷串列。
和單向鏈結串列的差異比較
| 特性 | 單向鏈結串列 | 雙向鏈結串列 |
|---|---|---|
| 節點結構 | 包含資料與下一個節點的指標 | 包含資料、前一個與下一個節點的指標 |
| 插入/刪除效率 | 相對簡單 | 需要更新前後節點的指標 |
| 記憶體使用 | 只需額外儲存一個指標 | 需額外儲存兩個指標 |
| 遍歷方向 | 僅能單向遍歷 | 可正向與反向遍歷 |
雙向鏈結串列雖然需要更多的記憶體來儲存指標,但它的雙向遍歷特性,使得操作更加靈活,適合某些需要快速插入、刪除以及雙向遍歷的應用場景。
使用場景與優缺點
✅ 雙向遍歷
- 雙向鏈結串列的最大優勢是可以從頭到尾,也可以從尾到頭遍歷,非常適合需要雙向操作的場景:
實際應用
- 音樂播放器的播放列表:支持上一首與下一首歌曲切換。
- 編輯器的撤銷與重做功能:需要追蹤前後狀態。
-✅ 插入與刪除效率高 - 和單向鏈結串列一樣,雙向鏈結串列在插入與刪除操作上也非常高效,因為只需要調整相關指標即可。
- ❌ 記憶體開銷
- 雙向鏈結串列需要額外的記憶體來儲存前一個節點的指標,因此在記憶體使用效率上不如單向鏈結串列與陣列。
結構與術語
節點 (Node) 的概念
雙向鏈結串列的節點由以下三部分組成:
- 資料 (data):存放節點的值或資訊。
- 前指標 (prev):指向前一個節點的指標。
- 後指標 (next):指向下一個節點的指標。
class Node {
int data;
Node prev;
Node next;
public Node(int data) {
this.data = data;
this.prev = null;
this.next = null;
}
}頭節點 (Head) 與尾節點 (Tail)
頭節點 (Head):雙向鏈結串列的起始節點,prev 指標為 null。 尾節點 (Tail):雙向鏈結串列的結尾節點,next 指標為 null。
實作與程式碼範例
雙向鏈結串列 (Doubly Linked List) 的實作
class DoublyLinkedList {
private Node head;
private Node tail;
public DoublyLinkedList() {
this.head = null;
this.tail = null;
}
// 新增節點到頭部
public void addFirst(int data) {
Node newNode = new Node(data);
if (head == null) {
head = tail = newNode;
} else {
newNode.next = head;
head.prev = newNode;
head = newNode;
}
}
// 新增節點到尾部
public void addLast(int data) {
Node newNode = new Node(data);
if (tail == null) {
head = tail = newNode;
} else {
newNode.prev = tail;
tail.next = newNode;
tail = newNode;
}
}
// 插入節點到指定位置
public void insert(int index, int data) {
if (index < 0) {
return;
}
if (index == 0) {
addFirst(data);
return;
}
Node current = head;
for (int i = 0; i < index - 1 && current != null; i++) {
current = current.next;
}
if (current == null) {
return;
}
Node newNode = new Node(data);
newNode.next = current.next;
if (current.next != null) {
current.next.prev = newNode;
} else {
tail = newNode;
}
newNode.prev = current;
current.next = newNode;
}
// 刪除頭部節點
public void removeFirst() {
if (head == null) {
return;
}
if (head.next == null) {
head = tail = null;
} else {
head = head.next;
head.prev = null;
}
}
// 刪除尾部節點
public void removeLast() {
if (tail == null) {
return;
}
if (tail.prev == null) {
head = tail = null;
} else {
tail = tail.prev;
tail.next = null;
}
}
// 刪除指定位置的節點
public void remove(int index) {
if (index < 0 || head == null) {
return;
}
if (index == 0) {
removeFirst();
return;
}
Node current = head;
for (int i = 0; i < index && current != null; i++) {
current = current.next;
}
if (current == null) {
return;
}
if (current.next != null) {
current.next.prev = current.prev;
} else {
tail = current.prev;
}
if (current.prev != null) {
current.prev.next = current.next;
}
}
// 反向輸出鏈結串列
public void printReverse() {
Node current = tail;
while (current != null) {
System.out.print(current.data + " ");
current = current.prev;
}
System.out.println();
}
// 輸出鏈結串列
public void printList() {
Node current = head;
while (current != null) {
System.out.print(current.data + " ");
current = current.next;
}
System.out.println();
}
}測試
public class Main {
public static void main(String[] args) {
DoublyLinkedList list = new DoublyLinkedList();
list.addFirst(3);
list.addFirst(2);
list.addFirst(1);
list.addLast(4);
list.addLast(5);
list.insert(2, 10);
list.removeFirst();
list.removeLast();
list.remove(2);
list.printList(); // 輸出: 2 3 4
list.printReverse(); // 輸出: 4 3 2
}
}結論
雙向鏈結串列是單向鏈結串列的進階版本,適合需要雙向遍歷或頻繁插入/刪除的應用場景。雖然記憶體使用稍多,但帶來的靈活性在許多情況下是值得的。