3.1 二元搜尋樹
二元搜尋樹(binary search tree)是一類樹狀資料結構的統稱。簡單的二元搜尋樹是一種類似 ITree 的結構(為說明方便,我們讓樹中的元素皆為 Int):
data BTree = Nul | Nod BTree Int BTree
另有個附加限制:在所有內部節點 Nod t x u 中,t 之中的所有元素均小於 x, u 之中的所有元素均大於 x.
以下我們將此限制簡稱為「有序性」。
給定一個有序的二元搜尋樹 t,下述函數 search k t 判斷 k 是否出現在 t 之中:
search :: Int -> BTree -> Bool
search k Nul = False
search k (Nod t x u) | k < x = search k t
| k == x = True
| x < k = search k u
這是一個歸納定義。由於有序性,每遇到一個 Nod t x u 節點,我們可比較 k 與 x 的大小,藉以決定往哪一個子樹做搜尋。
如果二元樹是平衡的 --- 意指在樹中的每個 Nod t x u 之中,t 和 u 的元素數目大致相等,或著至少其比例為某個常數,search k (Nod t x u) 每次選擇往 t 或 u 搜尋時都會排除一定比例的元素。如此一來,搜尋一個含 n 個元素的樹可在 $O(\log n)$ 的時間內完成。
然而,維持「平衡」並不容易,也是各種二元搜尋樹各顯神通之處。
以下我們多給一些定義,以便更形式化地描述有序性。我們假設有個抽象的「集合」型別 Set a. 含單一元素 x 的集合寫成 singleton x; 聯集寫作 (∪), 並具有結合律、交換律等性質。下述函數 elems 傳回一個樹中的所有元素(類似練習 2.33 中的 tags):
elems :: BTree -> Set Int
elems Nul = []
elems (Nod t x u) = elems t ∪ singleton x ∪ elems u
有序性可表示為下列述語 sorted:
sorted :: BTree -> Bool
sorted Nul = True
sorted (Nod t x u) = all (<x) (elems t) ⋀ all (x<) (elems u) ⋀
sorted t ⋀ sorted u
其中 all :: (a -> Bool) -> Set a -> Bool 檢查一個集合中的元素是否均滿足某述語。函數 all 滿足以下性質:
all p empty = True
all p (singleton x) = p x
all p (s ∪ t) = all p s ⋀ all p t
在二元搜尋樹中插入新元素的函數可定義如下:
insert :: Int -> BTree -> BTree
insert k Nul = Nod Nul k Nul
insert k (Nod t x u) | k < x = Nod (insert k t) x u
| k == x = Nod t x u
| x < k = Nod t x (insert k u)
函數 insert 和 search 的結構很類似:都藉由比較 k 與 x 決定該往哪兒插入元素;函數 search 只在碰到 Nul 時才能傳回 False, 函數 insert k 也只將 Nod Nul k Nul 放在原本 Nul 出現之處 --- 新插入的節點永遠是最邊緣的葉節點。
我們自然會希望 insert 保持有序性:如果 t 是有序的,insert k t 也應該是。表示為定理如下:
定理 3.1
對所有 t 與 k, sorted t ==> sorted (insert k t).
在 t 之上做歸納證明。
基底情況 t := Nul 之中,sorted (Nod Nul k Nul) 顯然成立。
歸納情況有 k < x, k == x, x < k 三種。
以下我們只列出第一種。
狀況 t := Nod t x u, k < x:
sorted (insert k (Nod t x u))
<=> {- insert 之定義, k < x -}
sorted (Nod (insert k t) x u)
<=> {- sorted 之定義 -}
all (<x) (elems (insert k t)) ⋀ all (x<) (elems u) ⋀
sorted (insert k t) ⋀ sorted u
<=> {- 練習 3.1: elems (insert k t) = singleton k ∪ elems t -}
all (<x) (singleton k ∪ elems t) ⋀ all (x<) (elems u) ⋀
sorted (insert k t) ⋀ sorted u
<=> {- all 之性質 -}
k < x ⋀ all (<x) (elems t) ⋀ all (x<) (elems u) ⋀
sorted (insert k t) ⋀ sorted u
<== {- k < x, 歸納假設 -}
all (<x) (elems t) ⋀ all (x<) (elems u) ⋀
sorted t ⋀ sorted u
<=> {- sorted 之定義 -}
sorted (Nod t x u)
函數 insert 並不保證做出的樹能平衡 --- 恰恰相反,我們一不小心便會做出偏一邊的樹。例如,
insert 5 (insert 4 (insert 3 (insert 2 (insert 1 Nul))))
得到的結果會是:
Nod Nul 1 (Nod Nul 2 (Nod Nul 3 (Nod Nul 4 (Nod Nul 5 Nul))))
在這個樹中搜尋 5, 和在串列 [1,2,3,4,5] 之中尋找 5 基本上是一樣的。
許多建立在二元搜尋樹的進階資料結構會在插入元素時多做些操作,想辦法維持樹的平衡。 我們將在第3.2 節介紹的紅黑樹就是一個例子。
練習 3.1
證明對所有 t 與 k, elems (insert k t) = singleton k ∪ elems t.