摘要:
本章介紹了二叉查找樹的概念及操作。主要內容包括二叉查找樹的性質,如何在二叉查找樹中查找最大值、最小值和給定的值,如何找出某一個元素的前驅和后繼,如何在二叉查找樹中進行插入和刪除操作。在二叉查找樹上執行這些基本操作的時間與樹的高度成正比,一棵隨機構造的二叉查找樹的期望高度為O(lgn),從而基本動態集合的操作平均時間為θ(lgn)。
1、二叉查找樹
二叉查找樹是按照二叉樹結構來組織的,因此可以用二叉鏈表結構表示。二叉查找樹中的關鍵字的存儲方式滿足的特征是:設x為二叉查找樹中的一個結點。如果y是x的左子樹中的一個結點,則key[y]≤key[x]。如果y是x的右子樹中的一個結點,則key[x]≤key[y]。根據二叉查找樹的特征可知,采用中根遍歷一棵二叉查找樹,可以得到樹中關鍵字有小到大的序列。http://www.cnblogs.com/Anker/archive/2013/01/27/2878594.html介紹了二叉樹概念及其遍歷。一棵二叉樹查找及其中根遍歷結果如下圖所示:
書中給出了一個定理:如果x是一棵包含n個結點的子樹的根,則其中根遍歷運行時間為θ(n)。
問題:二叉查找樹性質與最小堆之間有什么區別?能否利用最小堆的性質在O(n)時間內,按序輸出含有n個結點的樹中的所有關鍵字?
2、查詢二叉查找樹
二叉查找樹中最常見的操作是查找樹中的某個關鍵字,除了基本的查詢,還支持最大值、最小值、前驅和后繼查詢操作,書中就每種查詢進行了詳細的講解。
(1)查找SEARCH
在二叉查找樹中查找一個給定的關鍵字k的過程與二分查找很類似,根據二叉查找樹在的關鍵字存放的特征,很容易得出查找過程:首先是關鍵字k與樹根的關鍵字進行比較,如果k大比根的關鍵字大,則在根的右子樹中查找,否則在根的左子樹中查找,重復此過程,直到找到與遇到空結點為止。例如下圖所示的查找關鍵字13的過程:(查找過程每次在左右子樹中做出選擇,減少一半的工作量)
書中給出了查找過程的遞歸和非遞歸形式的偽代碼:
1 TREE_SEARCH(x,k) 2 if x=NULL or k=key[x] 3 then return x 4 if(k<key[x]) 5 then return TREE_SEARCH(left[x],k) 6 else 7 then return TREE_SEARCH(right[x],k)
1 ITERATIVE_TREE_SEARCH(x,k) 2 while x!=NULL and k!=key[x] 3 do if k<key[x] 4 then x=left[x] 5 else 6 then x=right[x] 7 return x
(2)查找最大關鍵字和最小關鍵字
根據二叉查找樹的特征,很容易查找出最大和最小關鍵字。查找二叉樹中的最小關鍵字:從根結點開始,沿着各個節點的left指針查找下去,直到遇到NULL時結束。如果一個結點x無左子樹,則以x為根的子樹中,最小關鍵字就是key[x]。查找二叉樹中的最大關鍵字:從根結點開始,沿着各個結點的right指針查找下去,直到遇到NULL時結束。書中給出了查找最大最小關鍵字的偽代碼:
1 TREE_MINMUM(x) 2 while left[x] != NULL 3 do x=left[x] 4 return x
1 1 TREE_MAXMUM(x) 2 2 while right[x] != NULL 3 3 do x= right[x] 4 4 return x
(3)前驅和后繼
給定一個二叉查找樹中的結點,找出在中序遍歷順序下某個節點的前驅和后繼。如果樹中所有關鍵字都不相同,則某一結點x的前驅就是小於key[x]的所有關鍵字中最大的那個結點,后繼即是大於key[x]中的所有關鍵字中最小的那個結點。根據二叉查找樹的結構和性質,不用對關鍵字做任何比較,就可以找到某個結點的前驅和后繼。
查找前驅步驟:先判斷x是否有左子樹,如果有則在left[x]中查找關鍵字最大的結點,即是x的前驅。如果沒有左子樹,則從x繼續向上執行此操作,直到遇到某個結點是其父節點的右孩子結點。例如下圖查找結點7的前驅結點6過程:
查找后繼步驟:先判斷x是否有右子樹,如果有則在right[x]中查找關鍵字最小的結點,即使x的后繼。如果沒有右子樹,則從x的父節點開始向上查找,直到遇到某個結點是其父結點的左兒子的結點時為止。例如下圖查找結點13的后繼結點15的過程:
書中給出了求x結點后繼結點的偽代碼:
1 TREE_PROCESSOR(x) 2 if right[x] != NULL 3 then return TREE_MINMUM(right(x)) 4 y=parent[x] 5 while y!= NULL and x ==right[y] 6 do x = y 7 y=parent[y] 8 return y
定理:對一棵高度為h的二叉查找,動態集合操作SEARCH、MINMUM、MAXMUM、SUCCESSOR、PROCESSOR等的運行時間均為O(h)。
3、插入和刪除
插入和刪除會引起二叉查找表示的動態集合的變化,難點在在插入和刪除的過程中要保持二叉查找樹的性質。插入過程相當來說要簡單一些,刪除結點比較復雜。
(1)插入
插入結點的位置對應着查找過程中查找不成功時候的結點位置,因此需要從根結點開始查找帶插入結點位置,找到位置后插入即可。下圖所示插入結點過程:
書中給出了插入過程的偽代碼:
1 TREE_INSERT(T,z) 2 y = NULL; 3 x =root[T] 4 while x != NULL 5 do y =x 6 if key[z] < key[x] 7 then x=left[x] 8 else x=right[x] 9 parent[z] =y 10 if y=NULL 11 then root[T] =z 12 else if key[z]>key[y] 13 then keft[y] = z 14 else right[y] =z
插入過程運行時間為O(h),h為樹的高度。
(2)刪除
從二叉查找樹中刪除給定的結點z,分三種情況討論:
<1>結點z沒有左右子樹,則修改其父節點p[z],使其為NULL。刪除過程如下圖所示:
<2>如果結點z只有一個子樹(左子樹或者右子樹),通過在其子結點與父節點建立一條鏈來刪除z。刪除過程如下圖所示:
<3>如果z有兩個子女,則先刪除z的后繼y(y沒有左孩子),在用y的內容來替代z的內容。
書中給出了刪除過程的偽代碼:
1 TREE_DELETE(T,z) 2 if left[z] ==NULL or right[z] == NULL 3 then y=z 4 else y=TREE_SUCCESSOR(z) 5 if left[y] != NULL 6 then x=left[y] 7 else x=right[y] 8 if x!= NULL 9 then parent[x] = parent[y] 10 if p[y] ==NULL 11 then root[T] =x 12 else if y = left[[prarnt[y]] 13 then left[parent[y]] = x 14 else right[parent[y]] =x 15 if y!=z 16 then key[z] = key[y] 17 copy y's data into z 18 return y
定理:對高度為h的二叉查找樹,動態集合操作INSERT和DELETE的運行時間為O(h)。
4、實現測試
采用C++語言實現一個簡單的二叉查找樹,支持動態集合的基本操作:search、minmum、maxmum、predecessor、successor、insert和delete。設計的二叉查找樹結構如下所示:
1 template <class T>
2 class BinarySearchTreeNode 3 { 4 public: 5 T elem; 6 struct BinarySearchTreeNode<T> *parent; 7 struct BinarySearchTreeNode<T>* left; 8 struct BinarySearchTreeNode<T>* right; 9 }; 10
11 template <class T>
12 class BinarySearchTree 13 { 14 public: 15 BinarySearchTree(); 16 void tree_insert(const T& elem); 17 int tree_remove(const T& elem ); 18 BinarySearchTreeNode<T>* tree_search(const T& elem)const; 19 T tree_minmum(BinarySearchTreeNode<T>* root)const; 20 T tree_maxmum(BinarySearchTreeNode<T>* root)const; 21 T tree_successor(const T& elem) const; 22 T tree_predecessor(const T& elem)const; 23 int empty() const; 24 void inorder_tree_walk()const; 25 BinarySearchTreeNode<T>* get_root()const {return root;} 26 private: 27 BinarySearchTreeNode<T>* root; 28 };
完整程序如下所示:

1 #include <iostream> 2 #include <stack> 3 #include <cstdlib> 4 using namespace std; 5 6 template <class T> 7 class BinarySearchTreeNode 8 { 9 public: 10 T elem; 11 struct BinarySearchTreeNode<T> *parent; 12 struct BinarySearchTreeNode<T>* left; 13 struct BinarySearchTreeNode<T>* right; 14 }; 15 16 template <class T> 17 class BinarySearchTree 18 { 19 public: 20 BinarySearchTree(); 21 void tree_insert(const T& elem); 22 int tree_remove(const T& elem ); 23 BinarySearchTreeNode<T>* tree_search(const T& elem)const; 24 T tree_minmum(BinarySearchTreeNode<T>* root)const; 25 T tree_maxmum(BinarySearchTreeNode<T>* root)const; 26 T tree_successor(const T& elem) const; 27 T tree_predecessor(const T& elem)const; 28 int empty() const; 29 void inorder_tree_walk()const; 30 BinarySearchTreeNode<T>* get_root()const {return root;} 31 private: 32 BinarySearchTreeNode<T>* root; 33 }; 34 35 template <class T> 36 BinarySearchTree<T>::BinarySearchTree() 37 { 38 root = NULL; 39 } 40 41 template <class T> 42 void BinarySearchTree<T>::tree_insert(const T& elem) 43 { 44 if(!empty()) 45 { 46 BinarySearchTreeNode<T> *pnode = root; 47 BinarySearchTreeNode<T> *qnode = NULL; 48 BinarySearchTreeNode<T> *newnode = new BinarySearchTreeNode<T>; 49 newnode->elem = elem; 50 newnode->parent = NULL; 51 newnode->left = NULL; 52 newnode->right = NULL; 53 while(pnode) 54 { 55 qnode = pnode; 56 if(pnode->elem > elem) 57 pnode = pnode->left; 58 else 59 pnode = pnode->right; 60 } 61 if(qnode->elem > elem) 62 qnode->left = newnode; 63 else 64 qnode->right = newnode; 65 newnode->parent = qnode; 66 } 67 else 68 { 69 root = new BinarySearchTreeNode<T>; 70 root->elem = elem; 71 root->parent =NULL; 72 root->left = NULL; 73 root->right = NULL; 74 } 75 } 76 77 template <class T> 78 int BinarySearchTree<T>::tree_remove(const T&elem) 79 { 80 BinarySearchTreeNode<T> *pnode; 81 BinarySearchTreeNode<T> *parentnode,*snode; 82 pnode = tree_search(elem); 83 if(pnode != NULL) 84 { 85 parentnode = pnode->parent; 86 if(pnode->right == NULL || pnode->left == NULL) 87 { 88 if(pnode->right != NULL) 89 { 90 if(parentnode->left == pnode) 91 parentnode->left = pnode->right; 92 if(parentnode->right == pnode) 93 parentnode->right = pnode->right; 94 pnode->right->parent = parentnode; 95 } 96 else if(pnode->left != NULL) 97 { 98 if(parentnode->left == pnode) 99 parentnode->left = pnode->left; 100 if(parentnode->right == pnode) 101 parentnode->right = pnode->left; 102 pnode->left->parent = parentnode; 103 } 104 else 105 { 106 if(parentnode->left == pnode) 107 parentnode->left = NULL; 108 if(parentnode->right == pnode) 109 parentnode->right = NULL; 110 } 111 delete pnode; 112 } 113 else 114 { 115 snode = tree_search(tree_successor(pnode->elem)); 116 pnode->elem = snode->elem; 117 if(snode->parent->left == snode) 118 { 119 snode->parent->left = snode->right; 120 snode->right->parent = snode->parent->left; 121 } 122 if(snode->parent->right == snode) 123 { 124 snode->parent->right = snode->right; 125 snode->right->parent = snode->parent->right; 126 } 127 delete snode; 128 } 129 return 0; 130 } 131 return -1; 132 } 133 template <class T> 134 BinarySearchTreeNode<T>* BinarySearchTree<T>::tree_search(const T& elem)const 135 { 136 BinarySearchTreeNode<T> *pnode = root; 137 while(pnode) 138 { 139 if(pnode->elem == elem) 140 break; 141 else if(pnode->elem > elem) 142 pnode = pnode->left; 143 else 144 pnode = pnode->right; 145 } 146 return pnode; 147 } 148 149 template <class T> 150 T BinarySearchTree<T>::tree_minmum(BinarySearchTreeNode<T>* root)const 151 { 152 BinarySearchTreeNode<T> *pnode = root; 153 if(pnode->left) 154 { 155 while(pnode->left) 156 pnode = pnode->left; 157 } 158 return pnode->elem; 159 } 160 161 template <class T> 162 T BinarySearchTree<T>::tree_maxmum(BinarySearchTreeNode<T>* root)const 163 { 164 BinarySearchTreeNode<T> *pnode = root; 165 if(pnode->right) 166 { 167 while(pnode->right) 168 pnode = pnode->right; 169 } 170 return pnode->elem; 171 } 172 173 template <class T> 174 T BinarySearchTree<T>::tree_successor(const T& elem) const 175 { 176 BinarySearchTreeNode<T>* pnode = tree_search(elem); 177 BinarySearchTreeNode<T>* parentnode; 178 if(pnode != NULL) 179 { 180 if(pnode->right) 181 return tree_minmum(pnode->right); 182 parentnode = pnode->parent; 183 while(parentnode && pnode == parentnode->right) 184 { 185 pnode = parentnode; 186 parentnode = parentnode->parent; 187 } 188 if(parentnode) 189 return parentnode->elem; 190 else 191 return T(); 192 } 193 return T(); 194 } 195 template <class T> 196 T BinarySearchTree<T>::tree_predecessor(const T& elem)const 197 { 198 BinarySearchTreeNode<T>* pnode = tree_search(elem); 199 BinarySearchTreeNode<T>* parentnode; 200 if(pnode != NULL) 201 { 202 if(pnode->right) 203 return tree_maxmum(pnode->right); 204 parentnode = pnode->parent; 205 while(parentnode && pnode == parentnode->left) 206 { 207 pnode = parentnode; 208 parentnode = pnode->parent; 209 } 210 if(parentnode) 211 return parentnode->elem; 212 else 213 return T(); 214 } 215 return T(); 216 } 217 218 template <class T> 219 int BinarySearchTree<T>::empty() const 220 { 221 return (NULL == root); 222 } 223 224 template <class T> 225 void BinarySearchTree<T>::inorder_tree_walk()const 226 { 227 if(NULL != root) 228 { 229 stack<BinarySearchTreeNode<T>*> s; 230 BinarySearchTreeNode<T> *ptmpnode; 231 ptmpnode = root; 232 while(NULL != ptmpnode || !s.empty()) 233 { 234 if(NULL != ptmpnode) 235 { 236 s.push(ptmpnode); 237 ptmpnode = ptmpnode->left; 238 } 239 else 240 { 241 ptmpnode = s.top(); 242 s.pop(); 243 cout<<ptmpnode->elem<<" "; 244 ptmpnode = ptmpnode->right; 245 } 246 } 247 } 248 } 249 int main() 250 { 251 BinarySearchTree<int> bstree; 252 BinarySearchTreeNode<int>* ptnode,*proot; 253 bstree.tree_insert(32); 254 bstree.tree_insert(21); 255 bstree.tree_insert(46); 256 bstree.tree_insert(54); 257 bstree.tree_insert(16); 258 bstree.tree_insert(38); 259 bstree.tree_insert(70); 260 cout<<"inorder tree walk is: "; 261 bstree.inorder_tree_walk(); 262 proot = bstree.get_root(); 263 cout<<"\nmax value is: "<<bstree.tree_maxmum(proot)<<endl; 264 cout<<"min value is: "<<bstree.tree_minmum(proot)<<endl; 265 ptnode = bstree.tree_search(38); 266 if(ptnode) 267 cout<<"the element 38 is exist in the binary tree.\n"; 268 else 269 cout<<"the element 38 is not exist in the binary tree.\n"; 270 cout<<"the successor of 38 is: "<<bstree.tree_successor(38)<<endl; 271 cout<<"the predecessor of 38 is:"<<bstree.tree_predecessor(38)<<endl; 272 if(bstree.tree_remove(46) == 0) 273 cout<<"delete 46 successfully"<<endl; 274 else 275 cout<<"delete 46 failed"<<endl; 276 cout<<"inorder tree walk is: "; 277 bstree.inorder_tree_walk(); 278 exit(0); 279 }
程序測試結果如下所示:
二叉樹實現時候添加了一個父結點指針,方便尋找給定結點的前驅和后繼。二叉樹中刪除操作實現比較復雜,需要分類討論,我分三種情況進行討論,程序寫的有些繁瑣,可以進行優化。優化后的代碼如下:
1 template <class T> 2 int BinarySearchTree<T>::tree_delete(const T& elem) 3 { 4 //找到該元素對應的結點 5 BinarySearchTreeNode<T>* pnode = tree_search(elem); 6 if(NULL != pnode) 7 { 8 BinarySearchTreeNode<T> *qnode,*tnode; 9 //判斷結點是否有左右孩子 10 if(pnode->left == NULL || pnode->right == NULL) 11 qnode = pnode; //有一個左孩子或者一個右孩子和沒有左右孩子 12 else 13 qnode = tree_search(tree_successor(elem)); //有左右孩子 14 if(NULL != qnode->left) 15 tnode = qnode->left; 16 else 17 tnode = qnode->right; 18 if(NULL != tnode) 19 tnode->parent = qnode->parent; 20 if(qnode->parent == NULL) 21 root = tnode; 22 else 23 if(qnode == qnode->parent->left) 24 qnode->parent->left = tnode; 25 else 26 qnode->parent->right = tnode; 27 if(qnode != pnode) 28 pnode->elem = qnode->elem; //將后繼結點的值復制到要刪除的結點的值 29 delete qnode; 30 return 0; 31 } 32 return -1; 33 }
5、隨機構造二叉查找樹
二叉查找上各種基本操作的運行時間都是O(h),h為樹的高度。但是在元素插入和刪除過程中,樹的高度會發生改變。如果n個元素按照嚴格增長的順序插入,那個構造出的二叉查找樹的高度為n-1。例如按照先后順序插入7、15、18、20、34、46、59元素構造二叉查找樹,二叉查找樹結構如下所示: