線性 ADT:鏈結串列、堆疊與佇列
線性抽象資料型別是元素依序排列的資料結構,包括鏈結串列(linked list)、堆疊(stack)與佇列(queue)。本頁說明鏈結串列如何用節點與指標串接資料、如何插入與刪除節點,以及如何用陣列或鏈結串列實作堆疊與環狀佇列。本頁屬於 HL 專屬的 B4 單元。
HL 專屬內容
本頁屬於 B4.1,只有 HL 需要學。
鏈結串列如何運作?
鏈結串列由一連串 節點(node) 組成,每個節點存放 資料 與指向下一個節點的 指標(pointer/reference)。串列由 head 指向第一個節點,最後一個節點的指標為 None/null。
flowchart LR
H((head)) --> A["10 | next"] --> B["25 | next"] --> C["37 | next"] --> N((None))
- 單向鏈結串列(singly linked list):只能往後走。
- 雙向鏈結串列(doubly linked list):每個節點同時有
prev與next,可以雙向走訪,但多佔記憶體。 - 環狀鏈結串列(circular linked list):最後一個節點指回第一個,適合輪流處理(例如 Round Robin 排程)。
陣列和鏈結串列怎麼比較?
| 操作 | 陣列 | 鏈結串列 |
|---|---|---|
| 依索引存取第 i 個 | O(1) | O(n),必須從 head 一個個走 |
| 在開頭插入/刪除 | O(n),要搬移所有元素 | O(1),改指標即可 |
| 在已知節點後插入/刪除 | O(n) | O(1) |
| 大小 | 靜態陣列固定 | 動態,可隨時增減 |
| 記憶體 | 連續、無額外負擔 | 不連續,每個節點多存指標 |
如何實作鏈結串列?
class Node:
def __init__(self, data):
self.data = data
self.next = None
class LinkedList:
def __init__(self):
self.head = None
def insert_front(self, data): # O(1)
node = Node(data)
node.next = self.head
self.head = node
def append(self, data): # O(n):走到尾端
node = Node(data)
if self.head is None:
self.head = node
return
cur = self.head
while cur.next is not None:
cur = cur.next
cur.next = node
def search(self, target): # O(n)
cur = self.head
while cur is not None:
if cur.data == target:
return True
cur = cur.next
return False
def delete(self, target):
if self.head is None:
return False
if self.head.data == target: # 刪除第一個節點
self.head = self.head.next
return True
prev, cur = self.head, self.head.next
while cur is not None:
if cur.data == target:
prev.next = cur.next # 跳過 cur
return True
prev, cur = cur, cur.next
return False
def display(self):
items, cur = [], self.head
while cur:
items.append(str(cur.data))
cur = cur.next
print(" -> ".join(items) + " -> None")
刪除節點的關鍵是 讓前一個節點的 next 跳過要刪除的節點。Java 中沒有指標指向的節點會被垃圾回收(garbage collection)。
如何實作堆疊?
堆疊是 LIFO:只在頂端(top)操作。
| 操作 | 說明 |
|---|---|
push(x) |
放到頂端 |
pop() |
移除並回傳頂端元素;空堆疊時為 下溢(underflow) |
peek() |
查看頂端但不移除 |
is_empty()/is_full() |
用固定陣列實作時,滿了再 push 為 溢位(overflow) |
class Stack:
def __init__(self, capacity):
self.items = [None] * capacity
self.top = -1
def is_empty(self):
return self.top == -1
def is_full(self):
return self.top == len(self.items) - 1
def push(self, x):
if self.is_full():
raise OverflowError("stack overflow")
self.top += 1
self.items[self.top] = x
def pop(self):
if self.is_empty():
raise IndexError("stack underflow")
x = self.items[self.top]
self.top -= 1
return x
def peek(self):
return None if self.is_empty() else self.items[self.top]
如何實作環狀佇列?
用一般陣列實作佇列時,前端取出後空間無法再利用。環狀佇列(circular queue) 讓 front 與 rear 指標用 % capacity 繞回陣列開頭,重複利用空間。
class CircularQueue:
def __init__(self, capacity):
self.items = [None] * capacity
self.capacity = capacity
self.front = 0
self.size = 0
def enqueue(self, x):
if self.size == self.capacity:
raise OverflowError("queue is full")
rear = (self.front + self.size) % self.capacity
self.items[rear] = x
self.size += 1
def dequeue(self):
if self.size == 0:
raise IndexError("queue is empty")
x = self.items[self.front]
self.front = (self.front + 1) % self.capacity
self.size -= 1
return x
推薦影音
鏈結串列(HackerRank)
簡述:Gayle Laakmann McDowell 說明鏈結串列的節點結構、插入與刪除,並現場用 Java 實作,對應本頁鏈結串列實作一節。
堆疊與佇列(HackerRank)
簡述:說明堆疊與佇列的應用情境,並示範如何用鏈結串列實作兩者,可與本頁的陣列實作對照。
練習題
練習 1:鏈結串列操作
從空串列開始依序執行 insert_front(5)、append(9)、insert_front(2)、delete(5),display() 會輸出什麼?
參考答案
5 → 5 -> 9 → 2 -> 5 -> 9 → 刪除 5 後為 2 -> 9 -> None。
練習 2:計算長度
為 LinkedList 新增方法 length(),回傳節點數。
練習 3:括號配對
用堆疊寫一個函式 balanced(s),判斷字串中的 ()、[]、{} 是否正確配對。
練習 4:環狀佇列追蹤
容量 3 的環狀佇列依序執行:enqueue A、enqueue B、dequeue、enqueue C、enqueue D。寫出最後陣列內容與 front 的值。
參考答案
enqueue A → [A, _, _] front = 0;enqueue B → [A, B, _];dequeue 取出 A → front = 1;enqueue C → [A, B, C](索引 0 的 A 已無效);enqueue D → rear = (1 + 2) % 3 = 0 → [D, B, C],front = 1,佇列順序為 B、C、D。
練習 5:選擇實作
一個音樂播放清單經常在中間插入或刪除歌曲,但很少依位置直接跳到第 n 首。應用陣列還是雙向鏈結串列?
參考答案
雙向鏈結串列。在已知位置插入、刪除只要改指標 O(1),且可以往前(上一首)往後(下一首)走訪;很少依索引存取,所以鏈結串列 O(n) 的存取缺點影響不大。