5.2 二元搜尋樹BST#
概念說明#
二元搜尋樹(BST) 在每個節點上維護不變式:左子樹所有值 < 節點值 < 右子樹所有值。這條規則讓每次搜尋都能「比較一次、排除一半」——與二元搜尋同樣的邏輯,只是資料結構是樹而非陣列,因此插入刪除不需要搬移整段資料。
BST 同時提供三種能力:(1) O(log n) 的搜尋/插入/刪除(平衡時);(2) 中序走訪得到排序序列;(3) 高效的範圍查詢與「找前驅/後繼」。這是雜湊表做不到的——雜湊表是無序的。
操作步驟#
- search(x):從根出發,x 較小往左、較大往右;遇到 None 表示不存在。
- insert(x):同樣往下走,走到 None 時就是新節點的位置。
- min / max:一路往左 / 一路往右。
- delete(x):三種情況——
- 葉節點:直接移除。
- 只有一個小孩:用小孩頂替自己。
- 有兩個小孩:找右子樹的最小節點(後繼者),把它的值搬過來,再刪除後繼者原本的位置(後繼者至多一個小孩,轉化為前兩種情況)。
Python 實作#
class BST:
def __init__(self):
self.root = None
def search(self, x):
node = self.root
while node:
if x == node.val:
return node
node = node.left if x < node.val else node.right
return None
def insert(self, x):
if self.root is None:
self.root = TreeNode(x)
return
node = self.root
while True:
if x < node.val:
if node.left is None:
node.left = TreeNode(x)
return
node = node.left
elif x > node.val:
if node.right is None:
node.right = TreeNode(x)
return
node = node.right
else:
return # 重複值不插入
def delete(self, x):
self.root = self._delete(self.root, x)
def _delete(self, node, x):
if node is None:
return None
if x < node.val:
node.left = self._delete(node.left, x)
elif x > node.val:
node.right = self._delete(node.right, x)
else: # 找到了
if node.left is None: return node.right
if node.right is None: return node.left
succ = node.right # 右子樹最小者
while succ.left:
succ = succ.left
node.val = succ.val
node.right = self._delete(node.right, succ.val)
return node
def in_order(self):
out = []
def walk(n):
if n:
walk(n.left); out.append(n.val); walk(n.right)
walk(self.root)
return out
# 動手玩
t = BST()
for x in [50, 30, 70, 20, 40, 60, 80]:
t.insert(x)
print(t.in_order()) # [20, 30, 40, 50, 60, 70, 80](自動有序)
print(t.search(40) is not None) # True
t.delete(30) # 刪有兩個小孩的節點
print(t.in_order()) # [20, 40, 50, 60, 70, 80]
退化問題#
按 1, 2, 3, 4, 5 的順序插入,BST 會變成一條向右的鏈,樹高 = n,所有操作退化为 O(n)——搜尋變成線性掃描。這是 BST 的致命傷:效能取決於插入順序。解法是「自動維持平衡」,即第6章的 AVL 樹與紅黑樹。順帶一提:把亂序資料插入 BST 得到的樹,與對該資料做快排的比較過程同構,期望深度仍是 O(log n)。
複雜度分析#
| 操作 | 平均(隨機插入) | 最壞(退化成鏈) |
|---|---|---|
| search / insert / delete | O(log n) | O(n) |
| 中序走訪 | O(n) | O(n) |
練習#
- 實作
range_query(lo, hi):回傳所有落在 [lo, hi] 的值,要求只拜訪必要子樹(剪枝),時間 O(h + k)。 - 實作
kth_smallest(k):第 k 小的元素。提示:中序走訪數到第 k 個,或讓節點維護「子樹大小」。 - 給一串列,統計「依插入順序建 BST 後的樹高」,分別用升冪、降冪、亂序資料各測 1000 筆,印出樹高並與 log₂1000 ≈ 10 比較。