前面的文章介紹過二分查找、散列表查找;二分查找效率爲Θ(lgn)、二分查找要求數組是靜態的,即元素不能動態的插入和刪除,不然會耗費較多的時間;散列表查找效率能夠到達Θ(1),可是通常用於「等於性查找「,不能進行「範圍查找」;本文介紹的二叉查找樹,(1)能實現動態查找,查找效率仍能達到Θ(lgn);(2)也能方便的進行範圍查找;node
除了上面的查找,二叉查找樹還能實現動態的插入和刪除操做。而且指望效率能達到Θ(lgn)ios
Question 一、那麼,什麼是二叉查找樹呢?git
二叉查找樹,也叫二叉搜索樹、二叉排序樹。是一種特殊的二叉樹,數據結構能夠用二叉鏈表結構表示;二叉查找樹關鍵字知足以下特性:設x爲二叉查找樹中的一個結點。若是y是x的左子樹中的一個結點,則key[y]≤key[x]。若是y是x的右子樹中的一個結點,則key[x]≤key[y]github
二叉查找樹有一個很好的特性:中序遍歷二叉查找樹能輸出一組有序的節點序列;其中,中序遍歷運行時間爲Θ(n)。算法
Question 二、二叉排序樹各類操做的效率如何?數組
二叉樹能夠實現的操做包括:基本查詢,查詢最小、最大關鍵值、前驅、後繼,單個節點的插入和刪除操做。這些操做的效率與二叉樹的高度成正比,一顆隨機構造的二叉查找樹的指望高度爲O(lgn),從而基本動態集合的操做平均時間爲Θ(lgn),注意這裏只是指望效率,最壞狀況下爲Θ(n),在二叉查找樹存在的問題小節中會進行講解緣由及如何改進使其最壞狀況下達到Θ(lgn)數據結構
節點中包括關鍵字元素key,衛星數據,每一個節點還包含屬性left,right和parent,他們指向節點的左孩子、右孩子、和雙親。
1 //二叉查找樹節點定義,帶模板 2 template <class T> 3 class BinarySearchTreeNode 4 { 5 public: 6 T key;//節點值,這裏只有一個關鍵元素值,沒有附帶數據(衛星數據) 7 BinarySearchTreeNode<T>* parent;//指向雙親節點指針 8 BinarySearchTreeNode<T>* left;//指向左孩子節點指針 9 BinarySearchTreeNode<T>* right;//指向右孩子節點指針 10 };
二叉查找樹中最多見的操做是查找樹中的某個關鍵字,除了基本的查詢,還支持最大值、最小值、前驅和後繼查詢操做
2.2.一、基本查詢
這個過程從樹根開始查找,並沿着這棵樹中一條簡單路徑向下進行,對遇到的每一個節點x,比較關鍵字k(待查找的關鍵字)與x.key。若是兩個關鍵字相等,查找就終止。若是k小於x.key,查找在x的左子樹中繼續。對稱地,若是k大於x.key,查找在右子樹中繼續。重複此過程,知道找到或者遇到空節點爲止。下圖是查找元素4的流程
查找過程僞碼:
遞歸:
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.2.一、查詢最小、最大關鍵字
根據二叉查找樹的特徵,很容易查找出最大和最小關鍵字。查找二叉樹中的最小關鍵字:從根結點開始,沿着各個節點的left指針查找下去,直到遇到NULL時結束。若是一個結點x無左子樹,則以x爲根的子樹中,最小關鍵字就是key[x]。查找二叉樹中的最大關鍵字:從根結點開始,沿着各個結點的right指針查找下去,直到遇到NULL時結束。查找最大最小關鍵字的僞代碼:
最小關鍵字
1 TREE-MINIMUM(x) 2 while x.left != NIL 3 x = x.left 4 return x
最大關鍵字
1 TREE_MAXIMUM(x) 2 while x.right != NIL 3 do x= x.right 4 return x
2.2.一、查詢前驅和後繼
給定一個二叉查找樹中的結點,找出在中序遍歷順序下某個節點的前驅和後繼。若是樹中全部關鍵字都不相同,則某一結點x的前驅就是小於key[x]的全部關鍵字中最大的那個結點,後繼便是大於key[x]中的全部關鍵字中最小的那個結點。根據二叉查找樹的結構和性質,不用對關鍵字作任何比較,就能夠找到某個結點的前驅和後繼。
查找前驅步驟:先判斷x是否有左子樹,若是有則在left[x]中查找關鍵字最大的結點,便是x的前驅。若是沒有左子樹,則從x繼續向上執行此操做,直到遇到某個結點是其父節點的右孩子結點。則該父親節點即爲前驅節點。例以下圖查找結點7的前驅結點6過程:
查找後繼步驟:先判斷x是否有右子樹,若是有則在right[x]中查找關鍵字最小的結點,即便x的後繼。若是沒有右子樹,則從x的父節點開始向上查找,直到遇到某個結點是其父結點的左兒子的結點時爲止,則該父親節點即爲後繼節點。例以下圖查找結點13的後繼結點15的過程:
求x前驅節點的僞碼:
1 TREE_PREDECESSOR(x) 2 if left[x] != NULL 3 then return TREE_MAXIMUM(left(x)) 4 y=parent[x] 5 while y!= NULL and x ==left[y] 6 do x = y 7 y=parent[y] 8 return y
求x後繼節點的僞碼:
1 TREE_SUCCESSOR(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
插入結點的位置對應着查找過程當中查找不成功時候的結點位置,所以須要從根結點開始查找帶插入結點位置,找到位置後插入便可。下圖所示插入結點過程:
插入過程僞代碼:
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
刪除操做比較複雜,從二叉查找樹中刪除給定的節點z,分三種狀況:
<1>結點z沒有左右子樹,則修改其父節點p[z],使其爲NULL。
<2>若是結點z只有一個子樹(左子樹或者右子樹),那麼將這個孩子提高到樹中z的位置上,並修改z的父節點,用z的孩子來替換z.
<3>若是z有兩個子女,則先刪除z的後繼y(y沒有左孩子),在用y的內容來替代z的內容。
下面的分析把上述狀況分紅4種狀況進行詳細講解:
在寫刪除僞代碼以前,爲了在二叉查找樹內移動子樹,先定義一個子過程TRANSPLANT,它是用另外一顆子樹替換一顆子樹併成爲其雙親的孩子節點。當TRANSPLANT用一顆以v爲根的子樹來替換一顆以u爲根的子樹是,節點u的雙親就變爲節點v的雙親,而且最後v成爲u的雙親的相應孩子。
TRANSPLANT子過程僞代碼:
1 TRANSPLANT(T,u,v) 2 if u.p == NIL 3 T.root = v 4 elseif u == u.p.left//若是u是一個左孩子 5 u.p.left = v 6 else u.p.right = v//若是u是一個右孩子 7 if v != NIL 8 v.p = u.p
利用現成的TRANSPLANT過程,下面是二叉搜索樹T中刪除節點z的刪除過程:
1 TREE-DELETE(T,z) 2 if z.left == NIL //圖a,左孩子爲空,用z的右孩子替換掉z 3 TRANSPLANT(T,z,z.right) 4 elseif z.right == NIL////圖b,右孩子爲空,用z的左孩子替換掉z 5 TRANSPLANT(T,z,z.left) 6 else //左右孩子都存在 7 y = TREE-SUCCESSOR(z)//z的後繼 8 if y.p != z//y不是z的右孩子 9 TRANSPLANT(T,y,y.right)//y的右孩子替換掉y 10 y.right = z.right//z的右孩子轉換爲y的右孩子 11 y.right.p = y 12 TRANSPLANT(T,z,y)//用y替換z併成爲z的雙親的一個孩子 13 y.left = z.left//再用z的左孩子替換爲y的左孩子 14 y.left.p = y
二叉查找上各類基本操做的運行時間都是O(h),h爲樹的高度。可是在元素插入和刪除過程當中,樹的高度會發生改變。若是n個元素按照嚴格增加的順序插入,那個構造出的二叉查找樹的高度爲n-1。例如按照前後順序插入七、1五、1八、20、3四、4六、59元素構造二叉查找樹,二叉查找樹結構以下所示:
這樣就致使二叉查找樹的基本操做運行時間是Θ(n),解決方法就是"旋轉"二叉樹,使二叉樹"平衡",好比平衡二叉樹、紅黑樹等。這些在其餘文章裏面講解。
BinarySearchTree.h(二叉查找樹實現)
1 #include <iostream> 2 #include <stack> 3 using namespace std; 4 5 //二叉查找樹節點定義,帶模板 6 template <class T> 7 class BinarySearchTreeNode 8 { 9 public: 10 T key;//節點值,這裏只有一個值,沒有附帶數據 11 BinarySearchTreeNode<T>* parent;//指向雙親節點指針 12 BinarySearchTreeNode<T>* left;//指向左孩子節點指針 13 BinarySearchTreeNode<T>* right;//指向右孩子節點指針 14 }; 15 16 /* 17 二查查找樹功能實現類,包括節點查找,最大、最小節點查找, 18 前驅、後繼節點查找,插入節點,刪除節點功能實現,帶模板 19 */ 20 template <class T> 21 class BinarySearchTree 22 { 23 public: 24 BinarySearchTree(); 25 void tree_insert(const T& elem); 26 int tree_remove(const T& elem); 27 BinarySearchTreeNode<T>* tree_search(const T& elem)const; 28 BinarySearchTreeNode<T>* tree_minmum(BinarySearchTreeNode<T>* root)const; 29 BinarySearchTreeNode<T>* tree_maxmum(BinarySearchTreeNode<T>* root)const; 30 BinarySearchTreeNode<T>* tree_successor(const T& elem) const; 31 BinarySearchTreeNode<T>* tree_predecessor(const T& elem)const; 32 bool empty() const; 33 void inorder_tree_traverse()const; 34 BinarySearchTreeNode<T>* get_root()const { return root; } 35 void transplant(BinarySearchTreeNode<T>* u, BinarySearchTreeNode<T>* v); 36 private: 37 BinarySearchTreeNode<T>* root; 38 }; 39 /*構造函數*/ 40 template <class T> 41 BinarySearchTree<T>::BinarySearchTree() 42 { 43 root = NULL; 44 } 45 /*二叉查找樹插入操做實現 46 實現思路:插入結點的位置對應着查找過程當中查找不成功時候的結點位置, 47 所以須要從根結點開始查找帶插入結點位置,找到位置後插入便可 48 */ 49 template <class T> 50 void BinarySearchTree<T>::tree_insert(const T& elem) 51 { 52 if (!empty())//判斷二叉樹是否爲空,非空則找到合適節點插入 53 { 54 BinarySearchTreeNode<T> *pnode = root; 55 BinarySearchTreeNode<T> *qnode = NULL; 56 BinarySearchTreeNode<T> *newnode = new BinarySearchTreeNode<T>;//建立新節點 57 newnode->key = elem;//插入節點值 58 newnode->parent = NULL;//初始化雙親節點、左右孩子節點 59 newnode->left = NULL; 60 newnode->right = NULL; 61 while (pnode)//非空,查找插入節點位置 62 { 63 qnode = pnode; 64 if (pnode->key > elem)//當前節點值大於待插入節點值,向左子樹查找 65 pnode = pnode->left; 66 else//當前節點值不大於待插入節點值,向右子樹查找 67 pnode = pnode->right; 68 } 69 if (qnode->key > elem)//當前節點值大於待插入節點值,則插入到左孩子節點 70 qnode->left = newnode; 71 else//不然插入到右孩子節點 72 qnode->right = newnode; 73 newnode->parent = qnode;//設置新插入節點的雙親節點 74 } 75 else//二叉樹爲空,則插入節點爲根節點 76 { 77 root = new BinarySearchTreeNode<T>; 78 root->key = elem; 79 root->parent = NULL; 80 root->left = NULL; 81 root->right = NULL; 82 } 83 } 84 /*刪除節點調用的子過程transplant,用一顆子樹v替換另一顆子樹u併成爲其雙親的孩子節點 85 輸入:u-被替換的子樹指針,v-新替換的子樹指針 86 輸出:void 87 */ 88 template <class T> 89 void BinarySearchTree<T>::transplant(BinarySearchTreeNode<T>* u, BinarySearchTreeNode<T>* v) 90 { 91 if (u->parent == NULL) root = v; 92 else if (u == u->parent->left) u->parent->left=v; 93 else u->parent->right = v; 94 95 if (v != NULL) v->parent = u->parent; 96 } 97 /*節點刪除實現 98 輸入:要刪除的節點的關鍵字 99 輸出:刪除成功返回0 100 不存在此關鍵字則返回-1 101 */ 102 template <class T> 103 int BinarySearchTree<T>::tree_remove(const T&elem) 104 { 105 BinarySearchTreeNode<T> *pnode;//當前節點 106 BinarySearchTreeNode<T> *snode;//雙親節點、後繼節點 107 pnode = tree_search(elem);//查找待刪除節點 108 if (!pnode) return -1;//待刪除節點不存在,返回-1 109 if (pnode->left == NULL)//左孩子不存在,用右孩子直接替換待刪除節點(這個右孩子能夠是NULL,也能夠不是NULL) 110 transplant(pnode, pnode->right); 111 else if (pnode->right == NULL)//只存在左孩子,用左孩子直接替換待刪除節點 112 transplant(pnode,pnode->left); 113 else//左右孩子都存在 114 { 115 snode = tree_minmum(pnode->right);//求待刪除節點的後繼(因右孩子存在,因此必定是右子樹中最小的節點) 116 if (snode->parent != pnode)//後繼不是待刪除節點的右孩子, 117 { 118 transplant(snode, snode->right);//snode是後繼節點,因此無左子樹,右子樹替換掉snode 119 snode->right = pnode->right;//pnode的右孩子成爲snode的右孩子 120 snode->right->parent = snode; 121 } 122 transplant(pnode, snode);//snode替換掉pnode 123 snode->left = pnode->left;//處理snode的左子樹,及左子樹的雙親關係 124 snode->left->parent = snode; 125 } 126 return 0; 127 128 } 129 /*查找search實現 130 輸入:待查找的數據elem 131 輸出:查找成功,返回指向該節點的指針; 132 待查找數值不存在,返回空指針 133 */ 134 template <class T> 135 BinarySearchTreeNode<T>* BinarySearchTree<T>::tree_search(const T& elem)const 136 { 137 BinarySearchTreeNode<T> *pnode = root;//節點指針,指向根節點 138 //非遞歸查找過程 139 while (pnode)//節點非空 140 { 141 if (pnode->key == elem)//查找成功:指針指向節點關鍵值等於待查找數值,退出while 142 break; 143 else if (pnode->key > elem)//當前節點值大於查找的elem,則在當前節點左孩子二叉樹中進行查找 144 pnode = pnode->left; 145 else //當前節點值小於查找的elem,則在當前節點右孩子二叉樹中進行查找 146 pnode = pnode->right; 147 } 148 149 return pnode; 150 } 151 152 /*查找最小關鍵字 153 輸入:指向要查找的二叉樹的根節點的指針 154 輸出:返回指向最小關鍵字節點的指針 155 */ 156 template <class T> 157 BinarySearchTreeNode<T>* BinarySearchTree<T>::tree_minmum(BinarySearchTreeNode<T>* root)const 158 { 159 BinarySearchTreeNode<T> *pnode = root;//節點指針,指向根節點 160 161 while (pnode->left)//從根結點開始,沿着各個節點的left指針查找下去,直到遇到NULL時結束 162 pnode = pnode->left; 163 164 return pnode; 165 } 166 /*查找最d大關鍵字 167 輸入:指向要查找的二叉樹的根節點的指針 168 輸出:返回指向最大關鍵字節點的指針 169 */ 170 template <class T> 171 BinarySearchTreeNode<T>* BinarySearchTree<T>::tree_maxmum(BinarySearchTreeNode<T>* root)const 172 { 173 BinarySearchTreeNode<T> *pnode = root;//節點指針,指向根節點 174 175 while (pnode->right)//從根結點開始,沿着各個節點的left指針查找下去,直到遇到NULL時結束 176 pnode = pnode->right; 177 178 return pnode; 179 } 180 /*求節點後繼 181 輸入:當前節點的值 182 輸出: 當前節點的後繼節點的指針 183 */ 184 template <class T> 185 BinarySearchTreeNode<T>* BinarySearchTree<T>::tree_successor(const T& elem) const 186 { 187 BinarySearchTreeNode<T>* pnode = tree_search(elem);//查找值爲elem的節點,返回指針保存到pnode 188 BinarySearchTreeNode<T>* parentnode=NULL;//定義雙親節點 189 if (pnode != NULL)//當前節點存在 190 { 191 if (pnode->right)//當前節點存在右孩子,則後繼爲右子樹的最小關鍵字節點 192 return tree_minmum(pnode->right); 193 parentnode = pnode->parent;//不存在右孩子,取雙親節點 194 /* 195 //雙親節點不爲空,而且當前節點時雙親節點的右孩子時,一直尋找雙親節點, 196 //直到雙親節點爲空,或者當前節點是雙親節點的左孩子 197 */ 198 while (parentnode && pnode == parentnode->right) 199 { 200 pnode = parentnode; 201 parentnode = parentnode->parent; 202 } 203 if (parentnode)//雙親節點不爲空,返回 204 return parentnode; 205 else//爲空,無後繼節點 206 return NULL; 207 } 208 return NULL;//當前節點不存在 209 } 210 /*求節點前驅 211 輸入:當前節點的值 212 輸出: 當前節點的前驅節點的指針 213 */ 214 template <class T> 215 BinarySearchTreeNode<T>* BinarySearchTree<T>::tree_predecessor(const T& elem)const 216 { 217 BinarySearchTreeNode<T>* pnode = tree_search(elem);//查找值爲elem的節點,返回指針保存到pnode 218 BinarySearchTreeNode<T>* parentnode;//定義雙親節點 219 if (pnode != NULL)//當前節點存在 220 { 221 if (pnode->right) 222 return tree_maxmum(pnode->right);//當前節點存在左孩子,則後繼爲左子樹的最大關鍵字節點 223 parentnode = pnode->parent;//不存在左孩子,取雙親節點 224 /* 225 //雙親節點不爲空,而且當前節點是雙親節點的左孩子時,一直尋找雙親節點, 226 //直到雙親節點爲空,或者當前節點是雙親節點的右孩子 227 */ 228 while (parentnode && pnode == parentnode->left) 229 { 230 pnode = parentnode; 231 parentnode = pnode->parent; 232 } 233 if (parentnode)//雙親節點不爲空,返回 234 return parentnode; 235 else//爲空,無後繼節點 236 return NULL; 237 } 238 return NULL; 239 } 240 /*判斷二叉樹查找樹是否爲空 241 輸出:若爲空,返回true 242 非空,返回false 243 */ 244 template <class T> 245 bool BinarySearchTree<T>::empty() const 246 { 247 return (NULL == root); 248 } 249 /*二叉查找樹中序遍歷非遞歸實現 250 實現思路:藉助棧完成 251 */ 252 template <class T> 253 void BinarySearchTree<T>::inorder_tree_traverse()const 254 { 255 if (NULL != root) 256 { 257 stack<BinarySearchTreeNode<T>*> s; 258 BinarySearchTreeNode<T> *ptmpnode; 259 ptmpnode = root;//指向根節點 260 while (NULL != ptmpnode || !s.empty())//當前節點不空,或者棧不空 261 { 262 if (NULL != ptmpnode)//當前節點不爲空,入棧,置左孩子節點爲當前節點 263 { 264 s.push(ptmpnode);//入棧 265 ptmpnode = ptmpnode->left;//置左孩子節點爲當前節點 266 } 267 else//當前節點爲空,彈出棧頂元素並訪問該節點,而後置右孩子節點爲當前節點 268 { 269 ptmpnode = s.top();//彈出棧頂元素 270 s.pop(); 271 cout << ptmpnode->key << " ";//訪問該節點 272 ptmpnode = ptmpnode->right; //而後置右孩子節點爲當前節點 273 } 274 } 275 } 276 }
Main.cpp(主測試函數)
1 #include"BinarySearchTree.h" 2 int main() 3 { 4 BinarySearchTree<int> bstree; 5 BinarySearchTreeNode<int>* ptnode, *proot; 6 bstree.tree_insert(32); 7 bstree.tree_insert(21); 8 bstree.tree_insert(46); 9 bstree.tree_insert(54); 10 bstree.tree_insert(16); 11 bstree.tree_insert(38); 12 bstree.tree_insert(70); 13 cout << "inorder tree walk is: "; 14 bstree.inorder_tree_traverse(); 15 proot = bstree.get_root(); 16 cout << "\nmax value is: " << bstree.tree_maxmum(proot)->key << endl; 17 cout << "min value is: " << bstree.tree_minmum(proot)->key<< endl; 18 ptnode = bstree.tree_search(38); 19 if (ptnode) 20 cout << "the element 38 is exist in the binary tree.\n"; 21 else 22 cout << "the element 38 is not exist in the binary tree.\n"; 23 cout << "the successor of 38 is: " << bstree.tree_successor(38)->key << endl; 24 cout << "the predecessor of 38 is:" << bstree.tree_predecessor(38)->key << endl; 25 if (bstree.tree_remove(46) == 0) 26 cout << "delete 46 successfully" << endl; 27 else 28 cout << "delete 46 failed" << endl; 29 cout << "inorder tree walk is: "; 30 bstree.inorder_tree_traverse(); 31 exit(0); 32 }
運行結果:
(我的想法):二叉排序樹是否能夠用來進行對數組進行排序呢?效率是多少?堆排序也是構造二叉樹,他們的特色各是什麼?(設平均高度爲O(lgn))
答(若有不妥請大神指點):(1)能夠排序,首先把每一個節點插入到而叉查找樹中構造完整二叉查找樹,而後進行中序遍歷便可獲得有序序列;其中插入單個節點時間爲Θ(lgn),n個節點用時Θ(nlgn),因此二叉排序樹排序效率爲Θ(nlgn)。
(2)堆排序是一顆徹底二叉樹,排序效率爲Θ(nlgn),須要構造大頂堆或者小頂堆。邊調整堆邊交換元素排序。完成後進行層序遍歷便可獲得有序序列;
(3)二叉排序樹能夠不是徹底二叉樹,堆排序是一顆徹底二叉樹。
【1】 http://www.cnblogs.com/Anker/archive/2013/01/28/2880581.html
【2】http://www.cnblogs.com/huangxincheng/archive/2012/07/21/2602375.html
【3】《算法導論》