《算法导论》第十二章----二叉查找树

《算法导论》学习记录目录

查找树是一种支持包括查找、插入、找最小值、找出最大值、找出前趋、找出后继、删除动态集合操作的数据结构。

基本操作的时间与树的高度成正比,对于一棵含有n个结点的完全二叉树,基本操作的最坏情况运行时间为Θ(lgn),对于含有n个结点的树(不是完全二叉树),最坏的情况(线性链)运行时间为Θ(n)。

二叉查找树的性质:x为二叉查找树的一个结点,x_l 为x的左子树中的一个结点,那么x_l存储的关键字小于或者等于x存储的关键字;x_r为x的右子树中的一个结点,那么x_r存储的关键字大于或者等于x存储的关键字。

如下图所示:

《《算法导论》第十二章----二叉查找树》

 

二叉树可以用链表结构来表示,每个结点除了关键字和卫星数据外,还有3个指针,分别指向左右儿子结点和父结点。

 

关于树结点的插入:

1、树为空;这时候要插入到树的结点为树根。

2、树不为空;从根结点开始,不断与插入结点的关键字比较,如果插入结点比较大,那么就往根结点的右子树走,否则就往左子树走;然后继续与子树的根结点比较,直到为空结点,将要插入的结点插到该位置,并将其父指针指向正确的结点。

 

《《算法导论》第十二章----二叉查找树》
《《算法导论》第十二章----二叉查找树》

 1 /*
 2  * 插入函数,注意插入结点与其在树中对应的父结点的链接(需要记录父结点)。
 3  * 从根结点出发,不停用当前结点与插入的值比较,如果当前结点的值比较大就往当前结点的左儿子走,相反就往右儿子走,直到当前结点为空,
 4  * 在过程中记录当前结点的父结点。
 5  * 运行时间为O(h),h为树的高度。因为整个过程是一条沿着根结点下降的路径。
 6  */
 7 void Tree_Insert(Tree *T, int key){
 8     TreeNode *x;
 9     x = (TreeNode *)malloc(sizeof(TreeNode));           //新建结点,并将key值付给结点的数据
10     x->value = key;
11     x->parent = x->left = x->right = NULL;
12 
13     if(T->root == NULL)
14         T->root = x;                                    //如果树为空,x结点为根
15     else{
16         TreeNode *y = T->root;                          //y结点用来记录当前结点
17         TreeNode *z = NULL;                             //z结点用来记录当前结点的父结点
18         while(y != NULL){
19             z = y;
20             if(y->value > x->value)
21                 y = y->left;
22             else
23                 y = y->right;
24         }
25         x->parent = z;                                  //将x结点与其父结点链接
26         if(z->value > x->value)
27             z->left = x;
28         else
29             z->right = x;                               //x结点的父节点与x结点链接
30     }
31 }

View Code

 

下图为将关键字为C的结点插入到二叉查找树里:

《《算法导论》第十二章----二叉查找树》

 

查询也差不多,将想要查询的关键字与根结点比较(如同插入操作的第二种情况),不断的往下走,直到当前结点的关键字等于查询的关键字或者当前结点为空结点。

《《算法导论》第十二章----二叉查找树》
《《算法导论》第十二章----二叉查找树》

 1 /*
 2  * 查找函数,返回含关键值对应的结点指针
 3  */
 4 TreeNode * Tree_Search(TreeNode *x, int key){
 5     if(x == NULL || x->value == key)
 6         return x;
 7     
 8     if(x->value > key)
 9         Tree_Search(x->left, key);
10     else
11         Tree_Search(x->right, key);
12 }

View Code

因为二叉查找树的特殊性质,我们很容易就可以想到关键字最小的结点的位置一定在树的最左边,关键字最大的结点一定在树的最右边。

《《算法导论》第十二章----二叉查找树》
《《算法导论》第十二章----二叉查找树》

 1 /*
 2  * 找最小值,并返回最小值对应的结点指针
 3  */
 4 TreeNode * Tree_Minimum(TreeNode *x){
 5     TreeNode *r = x;
 6     while(r->left != NULL)
 7         r = r->left;
 8     return r;
 9 }
10 
11 /*
12  * 找最大值,并返回最大值对应的结点指针
13  */
14 TreeNode * Tree_Maximum(TreeNode *x){
15     TreeNode *r = x;
16     while(r->right != NULL)
17         r = r->right;
18     return r;
19 }

View Code

对于结点的前趋(具有小于该结点的关键字中最大者的那个结点)、后继(具有大于该结点的关键字中最小者的那个结点)。

用后继来举例:

如果该结点存在右儿子,则后继为以其右儿子为根的子树的最小值对应的结点
如果没有右儿子,则找x结点的最低的祖先结点并且x结点处于最低祖先结点的左儿子子树里。如果在右儿子子树里,x结点的值比祖先结点的值大。

《《算法导论》第十二章----二叉查找树》
《《算法导论》第十二章----二叉查找树》

 1 /*
 2  * 找某个结点的后继(关键字大于该结点中最小的那个结点)
 3  * 如果该结点存在右儿子,则后继为以其右儿子为根的子树的最小值对应的结点
 4  * 如果没有右儿子,则找x结点的最低的祖先结点并且x结点处于最低祖先结点的左儿子子树里。如果在右儿子子树里,x结点的值比祖先结点的值大。
 5  */
 6 TreeNode * Tree_Successor(TreeNode *x){
 7     TreeNode *z = x;
 8     if(z->right != NULL)
 9         return Tree_Minimum(z->right);
10     TreeNode *y = z->parent;
11     while(y!= NULL && z == y->right){
12         z = y;
13         y = y->parent;
14     }
15     return y;
16 }
17 
18 /*
19  * 找某个结点的前趋(关键字小于该结点中最大的那个结点)
20  * 与后继相反
21  */
22 TreeNode * Tree_Predecessor(TreeNode *x){
23     TreeNode *z = x;
24     if(z->left != NULL)
25         return Tree_Maximum(z->left);
26     TreeNode *y = z->parent;
27     while(y != NULL && z == y->left){
28         z = y;
29         y = y->parent;
30     }
31     return y;
32 }

View Code

对于结点的删除,我们要确定真正删除的结点是哪一个。

因为(假设要删除的结点为z):如果z没有左右儿子,那么我们就直接用空结点代替它;如果z只有一个儿子结点,就用该结点替代它(如图a、b);以上两种情况都是删除z结点,但是当z有左右儿子结点的时候,实际上就不是删除z结点,因为如果直接删除z结点,z结点与其父结点和儿子结点的联系就失去了,树就不完整了,所以我们应该找出z结点的后继,删除它,再将它的信息替换z的信息(如图c、d)。

《《算法导论》第十二章----二叉查找树》

《《算法导论》第十二章----二叉查找树》

图c中z结点的后继为y结点,因为y结点没有左儿子结点,所以为z的右子树的最小值—后继。

《《算法导论》第十二章----二叉查找树》

《《算法导论》第十二章----二叉查找树》
《《算法导论》第十二章----二叉查找树》

 1 /*
 2  * 删除结点函数,首先要确定真正删除的结点是那个。
 3  * 如果x没有子结点,直接将x的父结点对应的指针指向NULL
 4  * 如果x只有一个子节点,直接将x的父结点对应的指针指向x的子结点
 5  * 如果x有两个子结点,实际上要删除的不是x,而是x的后继,,再用x的后继的内容代替x的内容
 6  */
 7 void Tree_Delete(TreeNode *Root, TreeNode *x){
 8     TreeNode *y;
 9     TreeNode *z;
10     if(x->left == NULL || x->right == NULL)
11         y = x;
12     else
13         y = Tree_Successor(x);
14 
15     if(y->left != NULL) 
16         z = y->left;
17     else
18         z = y->right;
19 
20     if(z != NULL)
21         z->parent = y->parent;
22     if(y->parent == NULL)
23         Root = z;
24     else if(y == y->parent->left)
25         y->parent->left = z;
26     else
27         y->parent->right = z;
28     if(y != x)
29         x->value = y->value;
30 
31     free(y);
32 }

View Code

树遍历,将树中的所有关键字按特定顺序全部输出。特定顺序包括中序(关键字介于左右子树关键字之间)、前序(关键字位于左右子树关键字之前)、后序(关键字位于左右子树关键字之后)。

下列代码为原文的中序递归遍历实现:

《《算法导论》第十二章----二叉查找树》
《《算法导论》第十二章----二叉查找树》

 1 /*
 2  * 中序遍历
 3  * 按排列顺序输出树中的所有关键字
 4  */
 5 void Inorder_Tree_Walk(TreeNode *x){
 6     if(x != NULL){
 7         Inorder_Tree_Walk(x->left);
 8         printf("%d ", x->value);
 9         Inorder_Tree_Walk(x->right);
10     }
11 }

View Code

下列为完整代码:

《《算法导论》第十二章----二叉查找树》
《《算法导论》第十二章----二叉查找树》

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 
  4 #define MAX 21
  5 
  6 typedef struct TreeNode{
  7     int value;
  8     struct TreeNode * parent;
  9     struct TreeNode * left;
 10     struct TreeNode * right;
 11 }TreeNode;                              //树结点结构体,包含数据、左右儿子结点指针、父结点指针
 12 
 13 typedef struct{
 14     TreeNode * root;
 15 }Tree;                                  //树结构体,包含一个根结点
 16 
 17 void Tree_Insert(Tree *T, int key);     //插入函数
 18 
 19 void Tree_Delete(TreeNode *Root, TreeNode *x);  //删除函数
 20 
 21 TreeNode * Tree_Search(TreeNode *x, int key);   //查找函数
 22 
 23 void Inorder_Tree_Walk(TreeNode *Root);         //中序遍历
 24 
 25 void Inorder_Tree_Walk_Iterative(TreeNode *Root);
 26 
 27 TreeNode * Tree_Minimum(TreeNode *x);           //找最小值
 28         
 29 TreeNode * Tree_Maximum(TreeNode *x);           //找最大值
 30 
 31 TreeNode * Tree_Successor(TreeNode *x);         //找后继
 32 
 33 TreeNode * Tree_Predecessor(TreeNode *x);       //找前趋
 34 
 35 void free_mem(TreeNode *x);                     //释放内存
 36 
 37 int main(){
 38     Tree *T;
 39     T->root = NULL;
 40 
 41     int n, value, i;
 42     scanf("%d", &n);
 43     for(i = 1; i <= n; i++){
 44         scanf("%d", &value);
 45         Tree_Insert(T, value);
 46     }
 47     TreeNode *s = Tree_Search(T->root, 3);
 48     if(s != NULL)
 49         printf("%d\n", s->value);
 50     Inorder_Tree_Walk(T->root);
 51 
 52     printf("\n");
 53     printf("%d\n", Tree_Minimum(T->root)->value);
 54     printf("%d\n", T->root->value);
 55     printf("%d\n", Tree_Maximum(T->root)->value);
 56 
 57     printf("%d\n", Tree_Successor(s)->value);
 58     printf("%d\n", Tree_Predecessor(s)->value);
 59     Tree_Delete(T->root, s);
 60     Inorder_Tree_Walk(T->root);
 61     printf("\n");
 62 
 63     free_mem(T->root);
 64     return 0;
 65 }
 66 
 67 /*
 68  * 插入函数,注意插入结点与其在树中对应的父结点的链接(需要记录父结点)。
 69  * 从根结点出发,不停用当前结点与插入的值比较,如果当前结点的值比较大就往当前结点的左儿子走,相反就往右儿子走,直到当前结点为空,
 70  * 在过程中记录当前结点的父结点。
 71  * 运行时间为O(h),h为树的高度。因为整个过程是一条沿着根结点下降的路径。
 72  */
 73 void Tree_Insert(Tree *T, int key){
 74     TreeNode *x;
 75     x = (TreeNode *)malloc(sizeof(TreeNode));           //新建结点,并将key值付给结点的数据
 76     x->value = key;
 77     x->parent = x->left = x->right = NULL;
 78 
 79     if(T->root == NULL)
 80         T->root = x;                                    //如果树为空,x结点为根
 81     else{
 82         TreeNode *y = T->root;                          //y结点用来记录当前结点
 83         TreeNode *z = NULL;                             //z结点用来记录当前结点的父结点
 84         while(y != NULL){
 85             z = y;
 86             if(y->value > x->value)
 87                 y = y->left;
 88             else
 89                 y = y->right;
 90         }
 91         x->parent = z;                                  //将x结点与其父结点链接
 92         if(z->value > x->value)
 93             z->left = x;
 94         else
 95             z->right = x;                               //x结点的父节点与x结点链接
 96     }
 97 }
 98 
 99 /*
100  * 查找函数,返回含关键值对应的结点指针
101  */
102 TreeNode * Tree_Search(TreeNode *x, int key){
103     if(x == NULL || x->value == key)
104         return x;
105     
106     if(x->value > key)
107         Tree_Search(x->left, key);
108     else
109         Tree_Search(x->right, key);
110 }
111 
112 /*
113  * 找最小值,并返回最小值对应的结点指针
114  */
115 TreeNode * Tree_Minimum(TreeNode *x){
116     TreeNode *r = x;
117     while(r->left != NULL)
118         r = r->left;
119     return r;
120 }
121 
122 /*
123  * 找最大值,并返回最大值对应的结点指针
124  */
125 TreeNode * Tree_Maximum(TreeNode *x){
126     TreeNode *r = x;
127     while(r->right != NULL)
128         r = r->right;
129     return r;
130 }
131 
132 /*
133  * 找某个结点的后继(关键字大于该结点中最小的那个结点)
134  * 如果该结点存在右儿子,则后继为以其右儿子为根的子树的最小值对应的结点
135  * 如果没有右儿子,则找x结点的最低的祖先结点并且x结点处于最低祖先结点的左儿子子树里。如果在右儿子子树里,x结点的值比祖先结点的值大。
136  */
137 TreeNode * Tree_Successor(TreeNode *x){
138     TreeNode *z = x;
139     if(z->right != NULL)
140         return Tree_Minimum(z->right);
141     TreeNode *y = z->parent;
142     while(y!= NULL && z == y->right){
143         z = y;
144         y = y->parent;
145     }
146     return y;
147 }
148 
149 /*
150  * 找某个结点的前趋(关键字小于该结点中最大的那个结点)
151  * 与后继相反
152  */
153 TreeNode * Tree_Predecessor(TreeNode *x){
154     TreeNode *z = x;
155     if(z->left != NULL)
156         return Tree_Maximum(z->left);
157     TreeNode *y = z->parent;
158     while(y != NULL && z == y->left){
159         z = y;
160         y = y->parent;
161     }
162     return y;
163 }
164 
165 /*
166  * 中序遍历
167  * 按排列顺序输出树中的所有关键字
168  */
169 void Inorder_Tree_Walk(TreeNode *x){
170     if(x != NULL){
171         Inorder_Tree_Walk(x->left);
172         printf("%d ", x->value);
173         Inorder_Tree_Walk(x->right);
174     }
175 }
176 
177 /*
178  * 删除结点函数,首先要确定真正删除的结点是那个。
179  * 如果x没有子结点,直接将x的父结点对应的指针指向NULL
180  * 如果x只有一个子节点,直接将x的父结点对应的指针指向x的子结点
181  * 如果x有两个子结点,实际上要删除的不是x,而是x的后继,,再用x的后继的内容代替x的内容
182  */
183 void Tree_Delete(TreeNode *Root, TreeNode *x){
184     TreeNode *y;
185     TreeNode *z;
186     if(x->left == NULL || x->right == NULL)
187         y = x;
188     else
189         y = Tree_Successor(x);
190 
191     if(y->left != NULL) 
192         z = y->left;
193     else
194         z = y->right;
195 
196     if(z != NULL)
197         z->parent = y->parent;
198     if(y->parent == NULL)
199         Root = z;
200     else if(y == y->parent->left)
201         y->parent->left = z;
202     else
203         y->parent->right = z;
204     if(y != x)
205         x->value = y->value;
206 
207     free(y);
208 }
209 
210 void free_mem(TreeNode *x){
211     if(x != NULL){
212         free_mem(x->left);
213         free_mem(x->right);
214         free(x);
215     }
216 }

View Code

因为这段时间比较忙(写实验的代码、做作业、看关于Linux的书、看具体数学、突然还开始看SICP。。。晕),一直都没时间写(好烂的借口)。。。。改天会将自己觉得应该添加的完善(前序、后序,迭代版本等等)。。。

继续努力!!!!

博客能更新真的很开心!!!

 

    原文作者:alan_forever
    原文地址: https://www.cnblogs.com/alan-forever/p/3404944.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞