6.1 AVL樹與旋轉#
概念說明#
AVL 樹 是第一種自平衡二元搜尋樹,不變式為:每個節點的左右子樹高度差(平衡因子)為 −1、0 或 +1。可證明滿足此條件的樹高 ≤ 1.44·log₂(n+2),因此所有操作穩定 O(log n)。
插入或刪除後,若某節點的平衡因子變成 ±2,就用旋轉修復。依「破壞路徑的方向」分四種情況,但實質只有兩種手術:
- LL 型(左邊太高,破壞節點在左小孩的左子樹)→ 右旋轉
- RR 型 → 左旋轉
- LR 型(左小孩的右子樹)→ 先對左小孩左旋,再對自己右旋
- RL 型 → 先對右小孩右旋,再對自己左旋
旋轉步驟#
右旋轉(對 z,其左小孩為 y):
y接替z的位置(由z的父節點指向y)。z變成y的右小孩。y原本的右小孩轉給z當左小孩。
左旋轉完全對稱。旋轉不改變中序順序,故 BST 性質完好。實作上常用「遞迴插入 + 回傳新子樹根」的寫法,讓父節點的指標自動接好。
Python 實作#
class AVLNode:
def __init__(self, val):
self.val = val
self.left = None
self.right = None
self.height = 1 # 葉高 1
def h(n): return n.height if n else 0
def update(n): n.height = 1 + max(h(n.left), h(n.right))
def balance(n): return h(n.left) - h(n.right)
def rotate_right(z):
y = z.left
t3 = y.right
y.right = z
z.left = t3
update(z); update(y) # 先更新低的 z
return y # 新子樹根
def rotate_left(z):
y = z.right
t3 = y.left
y.left = z
z.right = t3
update(z); update(y)
return y
def rebalance(n):
update(n)
b = balance(n)
if b > 1: # 左太高
if balance(n.left) < 0: # LR 型
n.left = rotate_left(n.left)
return rotate_right(n) # LL 型
if b < -1: # 右太高
if balance(n.right) > 0: # RL 型
n.right = rotate_right(n.right)
return rotate_left(n) # RR 型
return n
def insert(n, val):
if n is None:
return AVLNode(val)
if val < n.val: n.left = insert(n.left, val)
elif val > n.val: n.right = insert(n.right, val)
else: return n
return rebalance(n)
class AVLTree:
def __init__(self):
self.root = None
def insert(self, val):
self.root = insert(self.root, val)
def height(self):
return h(self.root)
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
# 致命測試:有序插入(會讓普通 BST 退化成鏈)
t = AVLTree()
for x in range(1, 16):
t.insert(x)
print(t.height()) # 4(純 BST 此時高 15)
print(t.in_order()) # 仍是有序的 [1..15]
def check_avl(n): # 驗證平衡性質
if n is None:
return 0
lh, rh = check_avl(n.left), check_avl(n.right)
assert abs(lh - rh) <= 1, f"節點 {n.val} 失衡"
return 1 + max(lh, rh)
check_avl(t.root)
print("AVL 平衡檢查通過")
複雜度分析#
搜尋、插入、刪除皆 O(log n):路徑長度 O(log n),沿途各節點的旋轉與高度更新 O(1)。刪除最壞可能觸發 O(log n) 次旋轉(一路修上去),插入至多 2 次;空間 O(n)。實務上 AVL 適合「查多改少」的場景,因為它樹最矮、搜尋路徑最短。
練習#
- 手工追蹤:依序插入
10, 20, 30, 40, 50, 25,寫出每次觸發的旋轉類型與最終樹形。 - 實作 AVL 的
delete:刪除後沿回溯路徑對每個節點呼叫rebalance,並用 1000 筆隨機資料驗證刪除後仍平衡。 - 統計實驗:分別用「BST 亂序插入」與「AVL 插入」處理 10 萬筆隨機數,比較兩者的樹高與 10 萬次搜尋的總耗時。