BinarySearchTree-二叉搜索树

一、二叉搜索树的定义及性质

二叉查找树(Binary Search Tree),也称有序二叉树(ordered binary tree),排序二叉树(sorted binary tree),是指一棵空树或者具有下列性质的二叉树:

1. 每个节点都有一个作为搜索依据的关键码( key) , 所有节点的关键码互不相同。
2. 左子树上所有节点的关键码( key) 都小于根节点的关键码( key) 。
3. 右子树上所有节点的关键码( key) 都大于根节点的关键码( key) 。
4. 左右子树都是二叉搜索树。

在实现中,由它的性质可以初步简单的定义出节点信息,我们需要定义一个内部类BinarySearchTreeNode,它包含两个分别指向左右节点的Node->_left和Node->_right,一个保存键值信息的Key。

 1 template <class K>
 2 struct BinarySearchTreeNode
 3 {
 4     BinarySearchTreeNode<K>* _left;  //指向左子树
 5     BinarySearchTreeNode<K>* _right;  //指向右子树
 6     K _key;  //关键码
 7     BinarySearchTreeNode(const K& key) 
 8         :_left(NULL)
 9         ,_right(NULL)
10         ,_key(key)
11     {}
12 };

 如下图,这个是普通的二叉树,它具有普通二叉树的所有性质。

在此基础上,加上上面所述节点之间的大小关系,就是二叉查找树:

下面这个是二叉搜索树,可以很清晰的看出节点之间的大小关系:

而二叉搜索树里面真正有意义的是对其节点的增、删、查找等操作,下面我分别来介绍这几种算法原理。

二、算法操作

由内部节点构建二叉搜索树如下:

 1 template<class K>
 2 class     BinarySearchTree
 3 {
 4     typedef BinarySearchTreeNode<K> Node;
 5 public:
 6     BinarySearchTree()
 7         :_root(NULL)
 8     {}
 9     ~BinarySearchTree()
10     {
11         _Delete(_root);
12     }
13     //非递归
14     bool Insert(const K& key)  //插入
15     {
16         return _Insert(_root, key);
17     }
18     bool Remove(const K& key)  //删除
19     {
20         return _Remove(_root, key);
21     }
22     const Node* Find(const K& key)  //查找
23     {
24         return _Find(_root, key);
25     }
26     //递归
27     bool InsertR(const K& key)
28     {
29         return _InsertR(_root, key);
30     }
31     bool RemoveR(const K& key)
32     {
33         return _RemoveR(_root, key);
34     }
35     const Node* FindR(const K& key)
36     {
37         return _FindR(_root, key);
38     }    
39     void Inorder()  //遍历打印
40     {
41         _Inorder(_root);
42         cout << endl;
43     }    
44 private:
45     void _Delete(Node*& root);
46     bool _Insert(Node* root, const K& key);
47     bool _Remove(Node* root, const K& key);
48     Node* _Find(Node* root, const K& key);
49     bool _InsertR(Node*& root, const K& key);
50     bool _RemoveR(Node* & root, const K& key);
51     const Node*_FindR(Node* root, const K& key);
52     void _Inorder(Node* root);
53 private:
54     Node* _root;
55 };

1.查找

查找操作和二分查找类似,从根节点开始,将root->_key和要找的节点的key比较,如果root->_key小于要找的节点的key,那么就在左子树 _root->_left节点中查找,如果root->_key大于要找的节点的key,则在右子树_root->_right节点中查找,如果_root->_key和要找的节点的key相等,直接返回此节点。

查找操作图示如下:

该方法实现有递归和非递归两种。

非递归算法程序如下:

 1          const Node* Find(const K& key)
 2         {
 3             return _Find(_root, key);
 4         }
 5         Node* _Find(Node* root, const K& key)
 6         {
 7             while (root) {
 8                 if (root->_key > key)  //向左查找
 9                     root = root->_left;
10                 if (root->_key < key)  //向右查找
11                     root = root->_right;
12                 else
13                     return root;  //找到节点
14             }
15             return NULL;
16         }

递归算法:

 1         const Node* FindR(const K& key)
 2         {
 3             return _FindR(_root, key);
 4         }
 5         const Node*_FindR(Node* root, const K& key)
 6         {
 7             if (root == NULL)
 8                 return NULL;            
 9             if (root->_key > key)
10                 return _FindR(root->_left, key); //向左递归查找
11             if (root->_key < key)
12                 return _FindR(root->_right, key);  //向右递归查找
13             else
14                 return root;  //找到该节点
15         }

 2.插入

插入一个节点第一步与与查找类似,首先要找到应该插入节点的位置,因为插入节点后不能破坏二叉搜索树的性质。首先判断如果树为空,则直接动态开辟节点并初始化为key插入即可,插入的节点即为根节点。然后再向下查找应该插入节点的位置,查找方法与上一个查找算法类似,不同的是在查找的过程中要将应该插入的位置的父节点记录下来。找到该位置后,将parent->_key与插入的key进行比较,判断应该插入到父节点的左子树还是右子树。如果parent->_key小于要插入节点的key,那么就插入为父节点的右子树parent->_right = new Node(key);  //插入右子树;如果parent->_key大于要插入节点的key,那么就插入为父节点的左子树parent->_left = new Node(key);   //插入左子树。

插入操作图示如下:

同样,插入操作的实现也有递归和非递归两种方法。

 非递归法实现如下:

 1          bool Insert(const K& key)
 2         {
 3         return _Insert(_root, key);
 4         }
 5                 bool Insert(Node*& root, const K& key) {
 6         if (root == NULL){   //当树为空时,直接插入
 7             root = new Node(key);
 8             return true;
 9         }
10         Node* cur = root;    
11         Node* parent = cur;
12         while (cur) {  //找到要插入的位置
13             if (cur->_key > key) {  
14                 parent = cur;
15                 cur = cur->_left;
16             }
17             else if (cur->_key < key) {   
18                 parent = cur;
19                 cur = cur->_right;
20             }
21             else
22                 return false;
23         }
24         if (parent->_key < key)   //找到插入位置的父节点,判断应该是父节点的左子树还是右子树右
25             parent->_right = new Node(key);  //插入右子树
26         else
27             parent->_left = new Node(key);   //插入左子树
28         return true;
29     }

递归法实现如下:

 1         bool InsertR(const K& key)
 2         {
 3             return _InsertR(_root, key);
 4         }
 5         bool _InsertR(Node*& root, const K& key)   //注意:这里参数传递方法是传引用
 6         {
 7             if (root == NULL){
 8                 root = new Node(key);
 9                 return true;
10             }
11             if (root->_key > key)
12                 return _InsertR(root->_left, key);
13             if (root->_key < key)
14                 return _InsertR(root->_right, key);
15             else
16                 return false;   
17         }

3.删除

删除元素操作在二叉树的操作中应该是比较复杂的。但是只要一步一步分析的话其实也是比较容易实现的。首先判断该树是否为空,为空的话直接返回删除失败;该是不为空时,第一步是找到需要删除的节点,方法与前面的查找算法类似,不同的是也需要将删除的节点的父节点记录下来,以判断该节点删除以后需要调整父节点的哪一个子树。找到该节点后,复杂的地方才刚开始。

(1)当要删除的节点的左子树为空时,判断是否为该节点是否为根节点,若是,则直接删除,其右子树的根节点成为新的根节点;若不是则再次判断该节点在其父节点的哪个子树上。若该节点在其父节点的左子树上,则将该节点的右子树连到该节点的父节点的左子树上,然后将该节点删除;若该节点在其父节点的右子树上,则将该节点的右子树连到该节点的父节点的右子树上,然后将该节点删除。

(2)当要删除的节点的右子树为空时,判断是否为该节点是否为根节点,若是,则直接删除,其左子树的根节点成为新的根节点;若不是则再次判断该节点在其父节点的哪个子树上。若该节点在其父节点的左子树上,则将该节点的左子树连到该节点的父节点的左子树上,然后将该节点删除;若该节点在其父节点的右子树上,则将该节点的左子树连到该节点的父节点的右子树上,然后将该节点删除。(类似于(1))。

 (3)当要删除的节点的左右子树都不为为空时,首先查找该节点的右子树的最小节点,即找该节点的右子树的最左节点,让这个最小节点代替该节点的位置,然后将此最小节点删除,这样调整之后才能使此树依然为搜索二叉树,另外在查找最左节点时应将它的父节点记录下来(原理同(1)和(2))。找到该节点的右子树的最左节点后,将最左节点的值赋给要删除的节点,作为新的该节点,其原值被覆盖,后面再删除的就是找到的最左节点。删除之前判断若此最左节点在其父节点的左子树上,则将该节点的右子树连到该节点的父节点的左子树上,然后将该节点删除;若该节点在其父节点的右子树上(即右子树最左节点为右子树的根节点),则将该节点的右子树连到该节点的父节点的右子树上,然后将该节点删除。(还有另一种方法,就是找到该节点的右子树的最大节点,即最右节点,与该节点替换后再删除,原理相同,此处不在赘述)。

此原理叙述起来较复杂,下面请看图示情况分类:

当删除的节点只有1个子节点时,在左边和在右边原理类似:

当删除的节点有2个子节点时:

用具体的二叉查找树举例如下:

非递归法代码如下:

 1                 bool Remove(const K& key) {
 2         if (_root == NULL)
 3             return false;
 4         Node* del = _root;
 5         Node* parent = del;
 6         while (del) {
 7             if (del->_key < key) {  //向右搜索
 8                 parent = del;
 9                 del = del->_right;
10             }
11             if (del->_key > key) {  //向左搜索
12                 parent = del;
13                 del = del->_left;
14             }
15             if (del->_key == key) {  //要删除的节点找到
16                 Node* cur = del;
17                 if (cur->_left == NULL) {  //当此节点左子树为空
18                     if (_root->_key == key)   //删除根节点
19                         _root = _root->_right;
20                     else{
21                         if(parent->_left == cur)  
22                             parent->_left = cur->_right;   //当找到的节点在其父节点的左子树上
23                         else
24                             parent->_right = cur->_right;  //当找到的节点在其父节点的右子树上
25                     }
26                 }
27                 else if (cur->_right == NULL) {  //当此节点右子树为空
28                     if (_root->_key == key)
29                         _root = _root->_left;
30                     else {
31                         if (parent->_left == cur)
32                             parent->_left = cur->_left;
33                         else
34                             parent->_right = cur->_left;
35                     }
36                 }
37                 else{   //左右子树都不为空
38                     cur = cur->_right;
39                     while (cur->_left) {  //找右子树的最左节点
40                         parent = cur;
41                         cur = cur->_left;
42                     }
43                     del->_key = cur->_key;  //将最左节点的值给要删除的节点,作为新的该节点,其原值被覆盖
44                     del = cur;  //后面删除的就是找到的最左节点
45                     if (parent->_left == cur)  //此时的cur是最左节点
46                         parent->_left = cur->_right;
47                     else
48                         parent->_right = cur->_right;  //右子树最左节点为右子树的根节点
49                 }
50                 delete del;
51                 del = NULL;
52                 return true;
53             }
54         }
55         return false;
56     }

递归法删除节点代码如下:

 1         bool RemoveR(const K& key)
 2         {
 3             return _RemoveR(_root, key);
 4         }
 5         bool _RemoveR(Node* & root, const K& key)   //注意此处传引用
 6         {
 7             if (root == NULL)
 8                 return false;
 9             if (root->_key > key)
10                 return _RemoveR(root->_left, key);  //向左递归搜索
11             if (root->_key < key)
12                 return _RemoveR(root->_right, key);  //向右递归搜索
13             else{  //要删除的节点找到
14                 Node* del = root;
15                 if (root->_left == NULL)
16                     root = root->_right;
17                 if (root->_right == NULL)
18                     root = root->_left;
19                 else{
20                     Node* cur = root;
21                     Node* parent = cur;
22                     cur = cur->_right;
23                     while (cur->_left) {
24                         parent = cur;
25                         cur = cur->_left;
26                     }
27                     del->_key = cur->_key;
28                     del = cur;
29                     if (parent->_left = cur)
30                         parent->_left = cur->_right;
31                     else
32                         parent->_right = cur->_right;
33                 }
34                 delete del;
35                 del = NULL;
36                 return true;
37             }
38         }

三、算法分析与总结

二叉查找树的运行时间和树的形状有关,树的形状又和插入元素的顺序有关。在最好的情况下,节点完全平衡,从根节点到最底层叶子节点只有lgN个节点。在最差的情况下,根节点到最底层叶子节点会有N各节点。在一般情况下,树的形状和最好的情况接近。

在分析二叉查找树的时候,我们通常会假设插入的元素顺序是随机的。对于N个不同元素,随机插入的二叉查找树来说,其平均查找/插入的时间复杂度大约为2lnN。

前面有对二分查找时间复杂度的分析,对二叉查找树的理解可以类比于此。它和二分查找一样,插入和查找的时间复杂度均为lgN,但是在最坏的情况下仍然会有N的时间复杂度。原因在于插入和删除元素的时候,树没有保持平衡。我们追求的是在最坏的情况下仍然有较好的时间复杂度,这就是后面要讲的平衡查找树的内容了。

下面是二叉查找树的时间复杂度:

四、完整的二叉搜索树代码

  1 #include<iostream>
  2 using namespace std;
  3 
  4 template <class K>
  5 struct BinarySearchTreeNode
  6 {
  7     BinarySearchTreeNode<K>* _left;
  8     BinarySearchTreeNode<K>* _right;
  9     K _key;
 10     BinarySearchTreeNode(const K& key) 
 11         :_left(NULL)
 12         ,_right(NULL)
 13         ,_key(key)
 14     {}
 15 };
 16 
 17 template<class K>
 18 class     BinarySearchTree
 19 {
 20     typedef BinarySearchTreeNode<K> Node;
 21 public:
 22     BinarySearchTree()
 23         :_root(NULL)
 24     {}
 25     ~BinarySearchTree()
 26     {
 27         _Delete(_root);
 28     }
 29 //非递归
 30     bool Insert(const K& key)
 31     {
 32         return _Insert(_root, key);
 33     }
 34     bool Remove(const K& key)
 35     {
 36         return _Remove(_root, key);
 37     }
 38     const Node* Find(const K& key)
 39     {
 40         return _Find(_root, key);
 41     }
 42 //递归
 43     bool InsertR(const K& key)
 44     {
 45         return _InsertR(_root, key);
 46     }
 47     bool RemoveR(const K& key)
 48     {
 49         return _RemoveR(_root, key);
 50     }
 51     const Node* FindR(const K& key)
 52     {
 53         return _FindR(_root, key);
 54     }    
 55     void Inorder()
 56     {
 57         _Inorder(_root);
 58         cout << endl;
 59     }    
 60 private:
 61         void _Delete(Node*& root)
 62         {
 63             if (root)
 64             {
 65                 _Delete(root->_left);
 66                 _Delete(root->_right);
 67                 delete root;
 68                 root = NULL;
 69             }
 70             return;                
 71         }
 72     bool Insert(const K& key) {
 73         if (_root == NULL){   //当树为空时,直接插入
 74             _root = new Node(key);
 75             return true;
 76         }
 77         Node* cur = _root;
 78         Node* parent = cur;
 79         while (cur) {  //找到要插入的位置
 80             if (cur->_key > key) {  
 81                 parent = cur;
 82                 cur = cur->_left;
 83             }
 84             else if (cur->_key < key) {   
 85                 parent = cur;
 86                 cur = cur->_right;
 87             }
 88             else
 89                 return false;
 90         }
 91         if (parent->_key < key)   //找到插入位置的父节点,判断应该是父节点的左子树还是右子树右
 92             parent->_right = new Node(key);  //插入右子树
 93         else
 94             parent->_left = new Node(key);   //插入左子树
 95         return true;
 96     }
 97 
 98          bool Remove(const K& key) {
 99         if (_root == NULL)
100             return false;
101         Node* del = _root;
102         Node* parent = del;
103         while (del) {
104             if (del->_key < key) {  //向右搜索
105                 parent = del;
106                 del = del->_right;
107             }
108             if (del->_key > key) {  //向左搜索
109                 parent = del;
110                 del = del->_left;
111             }
112             if (del->_key == key) {  //要删除的节点找到
113                 Node* cur = del;
114                 if (cur->_left == NULL) {  //当此节点左子树为空
115                     if (_root->_key == key)   //删除根节点
116                         _root = _root->_right;
117                     else{
118                         if(parent->_left == cur)  
119                             parent->_left = cur->_right;   //当找到的节点在其父节点的左子树上
120                         else
121                             parent->_right = cur->_right;  //当找到的节点在其父节点的右子树上
122                     }
123                 }
124                 else if (cur->_right == NULL) {  //当此节点右子树为空
125                     if (_root->_key == key)
126                         _root = _root->_left;
127                     else {
128                         if (parent->_left == cur)
129                             parent->_left = cur->_left;
130                         else
131                             parent->_right = cur->_left;
132                     }
133                 }
134                 else{   //左右子树都不为空
135                     cur = cur->_right;
136                     while (cur->_left) {  //找右子树的最左节点
137                         parent = cur;
138                         cur = cur->_left;
139                     }
140                     del->_key = cur->_key;  //将最左节点的值给要删除的节点,作为新的该节点,其原值被覆盖
141                     del = cur;  //后面删除的就是找到的最左节点
142                     if (parent->_left == cur)  //此时的cur是最左节点
143                         parent->_left = cur->_right;
144                     else
145                         parent->_right = cur->_right;  //右子树最左节点为右子树的根节点
146                 }
147                 delete del;
148                 del = NULL;
149                 return true;
150             }
151         }
152         return false;
153     }
154         Node* _Find(Node* root, const K& key)
155         {
156             while (root) {
157                 if (root->_key > key)  //向左查找
158                     root = root->_left;
159                 if (root->_key < key)  //向右查找
160                     root = root->_right;
161                 else
162                     return root;  //找到节点
163             }
164             return NULL;
165         }
166 
167 
168         bool _InsertR(Node*& root, const K& key)
169         {
170             if (root == NULL){
171                 root = new Node(key);
172                 return true;
173             }
174             if (root->_key > key)
175                 return _InsertR(root->_left, key);
176             if (root->_key < key)
177                 return _InsertR(root->_right, key);
178             else
179                 return false;   
180         }
181         bool _RemoveR(Node* & root, const K& key)
182         {
183             if (root == NULL)
184                 return false;
185             if (root->_key > key)
186                 return _RemoveR(root->_left, key);  //向左递归搜索
187             if (root->_key < key)
188                 return _RemoveR(root->_right, key);  //向右递归搜索
189             else{  //要删除的节点找到
190                 Node* del = root;
191                 if (root->_left == NULL)
192                     root = root->_right;
193                 if (root->_right == NULL)
194                     root = root->_left;
195                 else{
196                     Node* cur = root;
197                     Node* parent = cur;
198                     cur = cur->_right;
199                     while (cur->_left) {
200                         parent = cur;
201                         cur = cur->_left;
202                     }
203                     del->_key = cur->_key;
204                     del = cur;
205                     if (parent->_left = cur)
206                         parent->_left = cur->_right;
207                     else
208                         parent->_right = cur->_right;
209                 }
210                 delete del;
211                 del = NULL;
212                 return true;
213             }
214 
215         }
216         const Node*_FindR(Node* root, const K& key)
217         {
218             if (root == NULL)
219                 return NULL;            
220             if (root->_key > key)
221                 return _FindR(root->_left, key);
222             if (root->_key < key)
223                 return _FindR(root->_right, key);
224             else
225                 return root;
226         }
227         void _Inorder(Node* root)
228         {
229             if (root)
230             {
231                 _Inorder(root->_left);
232                 cout << root->_key << " ";
233                 _Inorder(root->_right);
234             }            
235         }
236 private:
237     Node* _root;
238 };
239 //测试代码:
240 #include"SearchTree.h"
241 
242 void Test()
243 {
244     BinarySearchTree<int> tree;
245     tree.InsertR(5);
246     tree.InsertR(1);
247     tree.Insert(2);
248     tree.InsertR(3);
249     tree.Insert(4);
250     tree.InsertR(9);
251     tree.Insert(8);
252     tree.InsertR(6);
253     tree.Insert(7);
254     
255     cout << tree.FindR(5) << endl;
256     cout << tree.FindR(5) << endl;
257     cout << tree.FindR(9) << endl;
258     cout << tree.FindR(9) << endl; 
259     cout << tree.Find(5) << endl;
260     cout << tree.Find(5) << endl;
261     cout << tree.Find(9) << endl;
262     cout << tree.Find(9) << endl;
263 
264     tree.Inorder();
265 
266     tree.RemoveR(5);
267     tree.RemoveR(4);
268     tree.RemoveR(1);
269     tree.Remove(9);
270     tree.Remove(2);
271     tree.Remove(3);
272     tree.Remove(6);
273     tree.Remove(7);
274     tree.Remove(8);
275 
276     tree.~BinarySearchTree();
277     
278 }
279 int main()
280 {
281     Test();
282     getchar();
283     return 0;
284 }

部分测试代码的输出结果:

 

本文部分图示来自于:/yangecnu/p/Introduce-Binary-Search-Tree.htm

 

posted @ 2017-06-18 21:29 滴巴戈 阅读(...) 评论(...) 编辑 收藏