《算法导论》第十二章----二叉查找树
查找树是一种支持包括查找、插入、找最小值、找出最大值、找出前趋、找出后继、删除动态集合操作的数据结构。
基本操作的时间与树的高度成正比,对于一棵含有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 }
下图为将关键字为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 }
因为二叉查找树的特殊性质,我们很容易就可以想到关键字最小的结点的位置一定在树的最左边,关键字最大的结点一定在树的最右边。
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 }
对于结点的前趋(具有小于该结点的关键字中最大者的那个结点)、后继(具有大于该结点的关键字中最小者的那个结点)。
用后继来举例:
如果该结点存在右儿子,则后继为以其右儿子为根的子树的最小值对应的结点
如果没有右儿子,则找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 }
对于结点的删除,我们要确定真正删除的结点是哪一个。
因为(假设要删除的结点为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 }
树遍历,将树中的所有关键字按特定顺序全部输出。特定顺序包括中序(关键字介于左右子树关键字之间)、前序(关键字位于左右子树关键字之前)、后序(关键字位于左右子树关键字之后)。
下列代码为原文的中序递归遍历实现:
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 }
下列为完整代码:
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 }
因为这段时间比较忙(写实验的代码、做作业、看关于Linux的书、看具体数学、突然还开始看SICP。。。晕),一直都没时间写(好烂的借口)。。。。改天会将自己觉得应该添加的完善(前序、后序,迭代版本等等)。。。
继续努力!!!!
博客能更新真的很开心!!!