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


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

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

    基本操作的时间与树的高度成正比,对于一棵含有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
    ", s->value);
     50     Inorder_Tree_Walk(T->root);
     51 
     52     printf("
    ");
     53     printf("%d
    ", Tree_Minimum(T->root)->value);
     54     printf("%d
    ", T->root->value);
     55     printf("%d
    ", Tree_Maximum(T->root)->value);
     56 
     57     printf("%d
    ", Tree_Successor(s)->value);
     58     printf("%d
    ", Tree_Predecessor(s)->value);
     59     Tree_Delete(T->root, s);
     60     Inorder_Tree_Walk(T->root);
     61     printf("
    ");
     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。。。晕),一直都没时间写(好烂的借口)。。。。改天会将自己觉得应该添加的完善(前序、后序,迭代版本等等)。。。

    继续努力!!!!

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

  • 相关阅读:
    JAVAWE第一天
    01--2048实例开篇
    10--动作系统(四)动作类中的reverse方法
    09--动作系统(三)使用持续动作
    08--动作系统(二)使用即时动作
    【转】cocos2d-x windows开发环境配置
    07--动作系统(一)
    06--触摸事件响应
    05--简单场景切换与精灵创建
    04--简单菜单使用
  • 原文地址:https://www.cnblogs.com/alan-forever/p/3404944.html
Copyright © 2020-2023  润新知