算法導論讀書筆記(12)
二叉查找樹
以下圖所示,一棵二叉查找樹是按二叉樹結構來組織的。這樣的樹能夠用鏈表結構來表示,其中每個結點都是一個對象。結點中除了 key 域和衛星數據外,還包含域 left , right 和 p ,它們分別指向結點的左兒子,右兒子和父結點。若是某個兒子結點或父結點不存在,則相應域中的值爲 NIL
。根結點是樹中惟一的父結點爲 NIL
的結點。算法
在二叉查找樹(binary search tree)上執行的基本操做的時間與樹的高度成正比。對於一棵含 n 個結點的徹底二叉樹,這些操做的最壞狀況運行時間爲 Θ ( lg n )。可是,若是樹是含 n 個結點的線性鏈,則這些操做的最壞狀況運行時間爲 Θ ( n )。數據結構
二叉查找樹中關鍵字的存儲方式老是知足如下的 二叉查找樹 性質:指針
設 x 爲二叉查找樹中的一個結點。若是 y 是 x 的左子樹中的一個結點,則 y.key <= x.key 。若是 y 是 x 的右子樹中的一個結點,則 y.key >= x.key 。
code
根據二叉查找樹的性質,能夠用一個遞歸算法按排列順序輸出樹中的全部關鍵字。這種算法稱爲 中序遍歷算法 ,由於子樹根的關鍵字在輸出時介於左子樹和右子樹的關鍵字之間(相似地, 前序遍歷 中根的關鍵字在其左右子樹的關鍵字以前輸出,而 後序遍歷 中根的關鍵字在其左右子樹的關鍵字以後輸出)。 對象
INORDER-TREE-WALK(x) 1 if x != NIL 2 INORDER-TREE-WALK(x.left) 3 print x.key 4 INORDER-TREE-WALK(x.right)
定理
若是 x 是一棵包含 n 個結點的子樹的根,則調用 INORDER-TREE-WALK(x)
過程的時間爲 Θ ( n )。
blog
查詢二叉查找樹
對於二叉查找樹,最多見的查找操做除了 SEARCH
外,二叉查找樹還支持 MINIMUM
, MAXIMUM
, SUCCESSOR
和 PREDECESSOR
等查詢。對高度爲 h 的樹,它們均可以在 O ( h )時間內完成。 遞歸
查找
下面的過程在樹中查找一個給定的關鍵字。 it
TREE-SEARCH(x, k) 1 if x == NIL or k == x.key 2 return x 3 if k < x.key 4 return TREE-SEARCH(x.left, k) 5 else 6 return TREE-SEARCH(x.right, k)
該過程從樹的根結點開始進行查找,並沿樹降低。對碰到的每一個結點 x ,就比較 k 和 x.key 。若是這兩個關鍵字相同,則查找結束。若是 k 小於 x.key ,則繼續查找 x 的左子樹,若是 k 大於 x.key ,則繼續在 x 的右子樹中查找。也能夠用 while
循環來替代遞歸過程。table
ITERATIVE-TREE-SEARCH(x, k) 1 while x != NIL and k != x.key 2 if k < x.key 3 x = x.left 4 else 5 x = x.right 6 return x
最大關鍵字元素和最小關鍵字元素
要查找二叉樹中具備最小關鍵字的元素,只要從根結點開始,沿着各結點的 left 指針查找下去,直到遇到 NIL
時爲止。class
TREE-MINIMUM(x) 1 while x.left != NIL 2 x = x.left 3 return x
在以 x 爲根的子樹中,最小關鍵字能夠在 x.left 爲根的左子樹中找到。過程 TREE-MAXIMUM
的僞碼是對稱的:
TREE-MAXIMUM(x) 1 while x.right != NIL 2 x = x.right 3 return x
對高度爲 h 的樹,這兩個過程的運行時間都是 O ( h )。
前趨和後繼
給定一個二叉查找樹中的結點,有時要求找出其在中序遍歷順序下它的後繼。若是全部的關鍵字均不相同,則某一結點 x 的後繼即具備大於 x.key 中的關鍵字中最小者的那個結點。
TREE-SUCCESSOR(x) 1 if x.right != NIL 2 return TREE-MINIMUM(x.right) 3 y = x.p 4 while y != NIL and x == y.right 5 x = y 6 y = y.p 7 return y
TREE-SUCCESSOR
代碼中包含兩種狀況。
- 若是結點 x 的右子樹非空,則 x 的後繼即右子樹中的最左結點,能夠經過
TREE-MINIMUM(x.right)
過程找到。 - 若是結點 x 的右子樹爲空,且 x 有一個後繼 y ,則 y 是 x 的最低祖先結點,且 y 的左兒子也是 x 的祖先。
以下圖所示,包含關鍵字15的結點的後繼是包含關鍵字17的結點。包含關鍵字13的結點的後繼是包含關鍵字15的結點。
過程 TREE-PREDECESSOR
和 TREE-SUCCESSOR
對稱,其運行時間都是 O ( h )。
TREE-PREDECESSOR(x) 1 if x.left != NIL 2 return TREE-MAXIMUM(x.left) 3 y = x.p 4 while y != NIL and x == y.left 5 x = y 6 y = y.p 7 return y
插入和刪除
插入和刪除操做會引發二叉樹結構上的變化,這時,就要修改其數據結構,以保持二叉查找樹性質。
插入
爲將一個新值 v 插入到二叉查找樹 T 中,能夠調用 TREE-INSERT
。傳給該過程的參數是個結點 z ,而且有 z.key = v , z.left = NIL
和 z.right = NIL
。
TREE-INSERT(T, z) 1 y = NIL 2 x = T.root 3 while x != NIL 4 y = x 5 if z.key < x.key 6 x = x.left 7 else 8 x = x.right 9 z.p = y 10 if y == NIL 11 T.root = z // tree T was empty 12 elseif z.key < y.key 13 y.left = z 14 else 15 y.right = z
TREE-INSERT
過程從根結點開始,並沿樹降低。指針 x 跟蹤了這條路徑,而 y 始終指向 x 的父結點。過程根據 z.key 與 x.key 的比較結果,決定向左或向右轉。這到 x 成爲 NIL
爲止。這個 NIL
所佔位置即咱們想插入項 z 的地方。
刪除
將結點 z 從二叉查找樹 T 中刪除共有以下三種狀況,其中一種有一點難懂。
- 若是結點 z 沒有孩子結點,咱們就能夠直接使用
NIL
來代替該結點。 - 若是結點 z 只有一個子女,能夠直接用結點 z 的孩子結點替代 z 。
- 若是結點 z 有兩個子女,首先要找到結點 z 的後繼 y (它必定在 z 的右子樹中),並用 y 替代 z 在樹中的位置。
TREE-DELETE
過程用於從二叉查找樹 T 中刪除一個給定的結點 z ,它按以下四種狀況組織代碼。
- 若是結點 z 沒有左孩子,那麼咱們用右孩子替換 z ,其中右孩子可能爲
NIL
。右孩子爲NIL
即結點 z 沒有孩子結點的狀況。 - 若是結點 z 有且只有左孩子,那麼就用左孩子替代結點 z 。
- 不然,結點 z 有兩個子結點。首先找到 z 的後繼結點 y ,它位於 z 的右子樹且沒有左孩子。這裏咱們打算用 y 替換 z 。
- 若是 y 是 z 的右孩子,那麼直接用 y 替換 z 。
- 不然,用 y 本身的右孩子替換 y ,再用 y 來替換 z 。
此外還有一個子程序 TRANSPLANT
,用於子樹之間的替換。
TRANSPLANT(T, u, v) 1 if u.p == NIL 2 T.root = v 3 elseif u == u.p.left 4 u.p.left = v 5 else 6 u.p.right = v 7 if v != NIL 8 v.p = u.p
TREE-DELETE(T, z) 1 if z.left == NIL 2 TRANSPLANT(T, z, z.right) 3 elseif z.right == NIL 4 TRANSPLANT(T, z, z.left) 5 else 6 y = TREE-MINIMUM(z.right) 7 if y.p != z 8 TRANSPLANT(T, y, y.right) 9 y.right = z.right 10 y.right.p = y 11 TRANSPLANT(T, z, y) 12 y.left = z.left 13 y.left.p = y