数据与结构与算法: 红黑树 C语言描述

681 阅读13分钟

1 红黑树特性

  • 每个节点或者是黑色,或者是红色。
  • 根节点是黑色。
  • 每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NilNULL)的叶子节点!]
  • 如果一个节点是红色的,则它的子节点必须是黑色的。
  • 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。 具体参考Wiki百科-红黑树

验证可以去旧金山大学的在线数据结构网页:旧金山大学-visualization-RedBlack.html

2 构建红黑树

2.1 红黑树节点结构体

#include <stdlib.h>
#include <stdio.h>

//这里偷懒就应0代表黑色,1代表红色了
typedef struct RBTreeNode {
    int    data; //数据域
    int    color; //0黑色 1红色
    struct RBTreeNode *parent;//父亲结点
    struct RBTreeNode *left;  //左子结点
    struct RBTreeNode *right; //右子结点
} RBTreeNode;

struct RBTreeNode *LastRBTreeNode;//新node指针

2.2 新插入自平衡分析

一. 插入的是根节点
    @do 直接置黑即可

    8(红)  =>  8(黑)

二. 插入的是非根节点
    此时取决于父亲和叔叔的颜色

    (一). 父亲是黑色
        @do 此时插入不破坏红黑树平衡性,直接插入即可
            Parent
               |
             8(黑)
             /
           4(红)
    (二). 父亲是红色 (则祖父必定是黑色)  
        此时取决于叔叔颜色

        [1]. 叔叔是红色 (则叔叔必定无子结点)
            @do 父亲叔叔都置黑,祖父置红,对祖父递归自平衡
                Parent            Parent
                   |                 |
                8(黑)              8(红)
                 /  \               / \
              4(红)  10(红)  =>  4(黑) 10(黑)   => reblace(8)
               /                  /
            2(红)              2(红)

        [2]. 叔叔是黑色,即Nil节点或者黑色,nil比较常见,黑色在[1]自底向顶递归reblace时会出现
            ***思考:叔叔是黑色非Nil节点是否违背红黑树特性呢?***
            此时取决于父亲是祖父的左节点,和插入节点在父亲的左树还是右树
            1. 父亲是祖父的左节点
                1.1: 新插入节点在父亲的左树
                    @do 父亲置黑,祖父置红,对祖父右旋
                            Parent           Parent             Parent
                              |                |                   |
                            8(黑)   祖父置红   8(红)              4(黑)
                            /       父亲置黑   /      对8右旋     /   \
                        4(红)          =>    4(黑)      =>    2(红)  8(红) 
                        /                   /
                    2(红)                 2(红)

                1.2 新插入节点在父亲的右树
                    @do 转化为1.1场景
                            Parent               Parent            Parent            Parent
                               |                   |                 |                  |
                             8(黑)               8(黑)  祖父置红     8(红)              5(黑)
                            /       转化为1.1     /     父亲置黑      /      对8右旋     /   \
                        4(红)          =>      5(红)       =>     5(黑)        =>    4(红)  8(红) 
                            \                  /                   /
                            5(红)           4(红)             4(红)

            2. 父亲是祖父的右节点 (与1互为镜像)
                2.1 新插入节点在父亲的右树
                    @do 父亲置黑,祖父置红,对祖父左旋
                            Parent                    Parent               Parent
                              |                         |                     |
                            8(黑)        祖父置红      8(红)                 10(黑)
                                \        父亲置黑        \       对8左旋     /   \
                                10(红)     =>           10(黑)     =>    8(红)  12(红)
                                  \                       \
                                 12(红)                  12(红)

                2.2 新插入节点在父亲的右树
                    转化为2.1场景
                            Parent                Parent               Parent               Parent
                              |                     |                     |                     |
                            8(黑)                  8(黑)    祖父置红      8(红)                 9(黑)
                                \      转化为2.1      \     父亲置黑        \       对8左旋     /   \
                                10(红)    =>          9(红)    =>           9(黑)     =>    8(红)  10(红)
                                /                       \                    \
                             9(红)                     10(红)                 10(红)

2.3 左旋

/**
 * 左旋
 *        parent                    parent
 *          8                         12
 *       4     12                  8     50  
 *           9    50      =>    4    9      70
 *                   70                      
 */
RBTreeNode *left_rotation(RBTreeNode *root)
{
    struct RBTreeNode *new_root;
    new_root         = root->right;
    root->right      = new_root->left;
    //将9的父亲设置为老的root 即8
    if (new_root->left != NULL)
    {
        new_root->left->parent = root;
    }
    //新root的parent即老parent
    new_root->parent = root->parent;
    //然后处理老root的parent
    if (root->parent == NULL)
    {
        //老root是根节点
        new_root->parent = NULL;
    }else{
        //判断父亲左右
        if (new_root->parent->left == root)
        {
            new_root->parent->left  = new_root;
        }else{
            new_root->parent->right = new_root;
        }
    }
    root->parent   = new_root;
    new_root->left = root;
    return new_root;
}

2.4 右旋

/**
 * 右旋
 *        parent                    parent
 *          8                  4
 *       4     12           2     8
 *    2    6         =>  1      6   12
 * 1                                
 */
RBTreeNode *right_rotation(RBTreeNode *root)
{
    struct RBTreeNode *new_root;
    new_root         = root->left;
    root->left       = new_root->right;
    
    //将6的父亲设置为老的root 即8
    if (new_root->right != NULL)
    {
        new_root->right->parent = root;
    }
    //新root的parent即老parent
    new_root->parent = root->parent;
    
    //然后处理老root的parent
    if (root->parent == NULL)
    {
        //老root是根节点
        new_root->parent = NULL;
    }else{
        //判断父亲左右
        if (new_root->parent->left == root)
        {
            new_root->parent->left = new_root;
        }else{
            new_root->parent->right= new_root;
        }
    }
    
    new_root->right = root;
    root->parent    = new_root;
    return new_root;
}

2.5 插入

这里与AVL树不同的地方在于,AVL树的插入后,通过递归将高度自底向顶传递,插入完成后再自顶向低的检查恢复平衡性。 而红黑树每插入一个节点后,尚未平衡前,单单通过原树的根节点是无法判断平衡的,当然也可以自顶向低遍历来找出待平衡的节点,但这样子做是很低效的,故而用一个全局变量储存新插入节点,平衡时仅对该节点平衡即可。

RBTreeNode *getNode(int data, RBTreeNode *parent)
{
    struct RBTreeNode *node;
    node = (struct RBTreeNode *)malloc(sizeof(struct RBTreeNode));
    node->data  = data;
    node->parent= parent;
    node->color = 1;
    node->right = NULL;
    node->left  = NULL;
    LastRBTreeNode = node;
    return node;
}

RBTreeNode *insert(RBTreeNode *root, int data, RBTreeNode *parent)
{
    if (NULL == root)
    {
        return getNode(data, parent);
    }
    if (data >= root->data)   
    {
        root->right  = insert(root->right, data, root);
    }else{
        root->left   = insert(root->left, data, root);
    }
    return root;
}

RBTreeNode *inserRB(RBTreeNode *root, int data, RBTreeNode *parent)
{
    root = insert(root,data,parent);
    root = rebalance(root,LastRBTreeNode);
    return root;
}

2.6 自平衡

RBTreeNode *rebalance(RBTreeNode *head, RBTreeNode *root)
{
    if (root->parent == NULL)
    {
        root->color = 0;
        return root;
    }
    //父亲不是NULL
    //如果父亲是黑色,该插入不破坏平衡性
    if (root->parent->color == 0)
    {
        return head;
    }
    //父亲是红色 一定有祖父,且祖父为黑色
    RBTreeNode *parent, *gparent, *uncle;
    parent = root->parent;
    gparent = root->parent->parent;
    if (parent == gparent->left)
    {
        uncle = gparent->right;
        //如果叔叔为红色, 不为NULL一定就是红色,不然不符合红黑树特性
        if (uncle != NULL){
            //父亲叔叔都置黑,祖父置红,对祖父递归自平衡
            
            gparent->color = 1;
            parent->color  = 0;
            uncle->color   = 0;
            return rebalance(head, gparent);
        }else{
        //如果叔叔为黑色,Nil(NULL)也是黑色哈
            //祖父置红,父亲置黑,对祖父右旋
            gparent->color = 1;
            parent->color  = 0;
            if (root == parent->right)
            {
                //1.root与父节点交换 并把父节点设为新root的左节点,即转化为1.1
                gparent->left = root;

                root->parent = gparent;
                root->left = parent;

                parent->parent = root;
                parent->right = NULL;
            }
            return gparent == head ? right_rotation(gparent) : head;
        }
    }else{
        uncle = gparent->left;
        //如果叔叔为红色, 不为NULL一定就是红色,不然不符合红黑树特性
        if (uncle != NULL){
            //父亲叔叔都置黑,祖父置红,对祖父递归自平衡
            gparent->color = 1;
            parent->color  = 0;
            uncle->color   = 0;
            return rebalance(head, gparent);
        }else{
        //如果叔叔为黑色,Nil(NULL)也是黑色哈
            //祖父置红,父亲置黑,对祖父右旋
            gparent->color = 1;
            parent->color  = 0;
            if (root == parent->left)
            {
                //1.root与父节点交换 并把父节点设为新root的左节点,即转化为2.1
                gparent->right = root;

                root->parent = gparent;
                root->right = parent;

                parent->parent = root;
                parent->left = NULL;
            }
            return gparent == head ? left_rotation(gparent) : head;
        }
    }
}

3 删除节点

3.1 删除节点分析

一. 被删除节点无左右节点
    (一). 特殊情况 被删除节点是根节点
        直接删除
    (二). 被删除节点是红色
        删除不破坏平衡性,直接删除
    (三). 被删除节点是黑色,则必然存在兄弟节点
        取决于被删除节点在父亲的左树还是右树,以及兄弟节点情况
        [1]. 被删除节点在父亲的左树 4为被删除节点
            1. 兄弟节点是黑色
                1.1 兄弟节点无左右节点
                    1.1.1父亲节点是黑色
                    直接兄弟节点置红(这种情况只有下图这一种情况,可通过8,4,12,20,再删除20构造)
                             8(黑)       兄弟置红     8(黑)
                            /  \          =>           \
                         4(黑) 12(黑)                  12(红)

                    1.1.2父亲节点是红色
                            Parent                  Parent
                             |           父亲置黑      |
                             8(红)       兄弟置红     8(黑)
                            /  \           =>           \
                         4(黑) 12(黑)                  12(红)


                1.2 兄弟节点只有一个右节点,右节点必为红色
                            Parent                            Parent                          Parent
                              |                                 |                               |
                         8(可红可黑)          父色赋兄右      8(可红可黑)                       12(黑)
                            /  \                 =>              \           对8左旋           / \
                         4(黑) 12(黑)                            12(黑)         =>     8(可红可黑) 20(可红可黑)
                                 \                                 \
                                 20(红)                           20(可红可黑)
                1.3 兄弟节点只有一个左节点,左节点必为红色
                    转化为场景1.2
                            Parent                         Parent                         Parent
                              |                              |                              |
                         8(可红可黑)      转化为场景1.2       8(可红可黑)                    10(黑)
                            /  \          父色赋兄,兄左置黑     \           对8左旋         /  \
                         4(黑) 12(黑)          =>              10(黑)        =>    8(可红可黑) 12(可红可黑)
                                /                               \
                              10(红)                          12(可红可黑)

                1.4 兄弟节点有左右节点,必然都是红色
                    转化为场景1.2
                            Parent                         Parent                         Parent
                              |                              |                              |
                         8(可红可黑)                        8(可红可黑)                     12(黑)
                            /  \          父色赋兄右           \           对8左旋          /  \
                         4(黑) 12(黑)          =>              12(黑)        =>    8(可红可黑) 12(可红可黑)
                              /  \                            /  \                      \
                           10(红) 20(红)                   10(红) 20(可红可黑)           10(红)
            2. 兄弟节点是红色
                则父亲比为黑,兄弟节点必有俩黑色节点 (这种情况可通过8,4,12,10,20,30 再删除30构造)
                            Parent                         Parent                         Parent
                              |             兄弟置黑          |                              |
                            8(黑)           兄左置红         8(黑)                          12(黑)
                            /  \          转为为场景1.4         \       对8左旋             /  \
                         4(黑) 12(红)        =>              12(黑)        =>            8(黑) 20(黑)
                              /  \                            /  \                        \   
                           10(黑) 20(黑)                   10(红) 20(黑)                 10(红) 
        [2]. 被删除节点在父亲的右树 12为被删除节点 与[1]互为镜像
            1. 兄弟节点是黑色
                1.1 兄弟节点无左右节点
                    1.1.1父亲节点是黑色
                    直接兄弟节点置红(这种情况只有下图这一种情况,可通过8,4,12,20,再删除20构造)
                             8(黑)       兄弟置红     8(黑)
                            /  \          =>         /
                         4(黑) 12(黑)              4(红)

                    1.1.2父亲节点是红色
                            Parent                  Parent
                             |           父亲置黑      |
                             8(红)       兄弟置红     8(黑)
                            /  \           =>        /
                         4(黑) 12(黑)              4(红)


                1.2 兄弟节点只有一个左节点,左节点必为红色
                            Parent                            Parent                          Parent
                              |                                 |                               |
                         8(可红可黑)          父色赋兄左      8(可红可黑)                        4(黑)
                            /  \                 =>             /           对8右旋             / \
                         4(黑) 12(黑)                         4(黑)            =>       2(可红可黑) 8(可红可黑)
                          /                                   /
                       2(红)                              2(可红可黑)
                1.3 兄弟节点只有一个右节点,右节点必为红色
                    转化为场景1.2
                            Parent                         Parent                         Parent
                              |                              |                              |
                         8(可红可黑)      转化为场景1.2       8(可红可黑)                    6(黑)
                            /  \          父色赋兄右         /            对8右旋           /  \
                         4(黑) 12(黑)          =>          6(黑)            =>     4(可红可黑) 8(可红可黑)
                            \                             /
                             6(红)                     4(可红可黑)

                1.4 兄弟节点有左右节点,必然都是红色
                    转化为场景1.2
                            Parent                        Parent                         Parent
                              |                             |                              |
                         8(可红可黑)                       8(可红可黑)                     4(黑)
                            /  \          父色赋兄左        /           对8右旋            /   \
                         4(黑) 12(黑)         =>          4(黑)           =>    2(可红可黑)    8(可红可黑)
                          /  \                           /  \                                 /
                      2(红) 6(红)               2(可红可黑)  6(红)                            6(红)
            2. 兄弟节点是红色
                则父亲比为黑,兄弟节点必有俩黑色节点 (这种情况可通过8,4,12,2,6,1 再删除1构造)
                            Parent                         Parent                  Parent
                              |             兄弟置黑          |                       |
                            8(黑)           兄右置红         8(黑)                   4(黑)
                            /  \          转为为场景1.4      /       对8右旋         /  \
                         4(红) 12(黑)        =>            4(黑)       =>        2(黑) 8(黑)
                          /  \                            /  \                        /
                       2(黑) 6(黑)                     2(黑)  6(红)                  6(红)
二. 被删除节点只有一个左节点
    那么依据红黑树特性,被删除节点必然为黑色,且左节点为红色
    (一). 被删除节点在左树 4是待删除节点
                Parent                            Parent                      Parent
                   |                                |                          |
                8(可红可黑)          左置黑      8(可红可黑)     删除4,上提2    8(可红可黑) 
                  /  \                 =>         /   \           =>         /  \
              4(黑) 12(黑)                      4(黑) 12(黑)               2(黑) 12(黑)
               /                                / 
            2(红)                            2(黑)
    (一). 被删除节点在右树 12是待删除节点
                Parent                            Parent                      Parent
                   |                                |                          |
                8(可红可黑)          左置黑      8(可红可黑)   删除12,上提10    8(可红可黑) 
                  /  \                 =>         /   \           =>         /  \
              4(黑) 12(黑)                      4(黑) 12(黑)               2(黑) 10(黑)
                     /                                 / 
                  10(红)                             10(黑)
三. 被删除节点只有一个右节点 跟二是镜像关系 此处不赘述
四. 被删除节点左右节点都有,删除该节点,用前驱后继节点代替

3.2 查找前驱/后继节点

RBTreeNode *FindMin(RBTreeNode *root)
{
    if (NULL == root)
    {
        return NULL;
    }
    if (root->left == NULL)
    {
        return root;
    }else{
        return FindMin(root->left);
    }
}
RBTreeNode *FindMax(RBTreeNode *root)
{
    if (NULL == root)
    {
        return NULL;
    }
    if (root->right == NULL)
    {
        return root;
    }else{
        return FindMax(root->right);
    }
}

3.3 删除节点代码实现

RBTreeNode *Delete(RBTreeNode *head, RBTreeNode *root, int target)
{
    if (NULL == root)
    {
        return NULL;
    }
    if (target > root->data){
        return Delete(head, root->right, target);
    }else if (target < root->data)
    {
        return Delete(head, root->left, target);
    }else{
    //相等的情况
        RBTreeNode *parent, *brother;
        parent = root->parent;
        int head_flag = parent == head;
        int red_flag  = root->color == 1;
        //左右节点都没
        if (root->left == NULL && root->right == NULL)
        {
            //特殊情况删除根节点
            if (parent == NULL)
            {
                return NULL;//删除自身
            }
            //被删除节点在左树上
            if (root == parent->left)
            {
                parent->left = NULL;
                free(parent->left);
                root = NULL;
                free(root);
                //被删除节点是红色,直接删除
                if (red_flag) return head;              
                //被删除节点是黑色,则必然存在兄弟节点,兄弟节点可能黑也可能红
                brother = parent->right;
                //1.兄弟节点没有左右节点
                if (brother->left == NULL && brother->right == NULL)
                {
                    brother->color = 1;
                    return head;
                }else if(brother->left == NULL && brother->right != NULL)
                //2.兄弟节点只有一个右节点 兄弟节点必然为黑色 此右节点必然为红色
                {
                    brother->right->color = parent->color;
                    parent = left_rotation(parent);
                    return head_flag ? parent : head;
                }else if(brother->left != NULL && brother->right == NULL)
                //3.兄弟节点只有一个左节点 兄弟节点必然为黑色 必然为红色
                {
                    //转化为场景2
                    parent->right        = brother->left;
                    //对于brother->left
                    brother->left->right = brother;
                    brother->left->color = 0;
                    brother->left->parent= parent;
                    //对于brother
                    brother->parent      = brother->left;
                    brother->color       = parent->color;
                    brother->left        = NULL;
                    parent = left_rotation(parent);
                    return head_flag ? parent : head;
                }else
                //4.兄弟节点左右节点都有
                {
                    //兄弟黑,必有俩红
                    if (brother->color == 0)
                    {
                        brother->right->color = parent->color;
                    }else{
                    //兄弟红,必有俩黑,且父亲必黑
                        brother->color       = 0;
                        brother->left->color = 1;
                    }
                    parent = left_rotation(parent);
                    return head_flag ? parent : head;
                }
            }else
            //被删除节点在右树上 跟左树是镜像关系
            {
                parent->right = NULL;
                free(parent->right);
                root = NULL;
                free(root);
                //被删除节点是红色,直接删除
                if (red_flag) return head;              
                //被删除节点是黑色,则必然存在黑色兄弟节点
                brother = parent->left;
                //1.兄弟节点没有左右节点
                if (brother->left == NULL && brother->right == NULL)
                {
                    brother->color = 1;
                    return head;
                }else if(brother->left != NULL && brother->right == NULL)
                //2.兄弟节点只有一个左节点
                {
                    brother->left->color = parent->color;
                    parent = right_rotation(parent);
                    return head_flag ? parent : head;
                }else if(brother->left == NULL && brother->right != NULL)
                //3.兄弟节点只有一个右节点
                {
                    //转化为场景2
                    parent->left         = brother->right;
                    //对于brother->right
                    brother->right->left = brother;
                    brother->right->color = 0;
                    brother->right->parent= parent;
                    //对于brother
                    brother->parent      = brother->right;
                    brother->color       = parent->color;
                    brother->right       = NULL;
                    parent = right_rotation(parent);
                    return head_flag ? parent : head;
                }else
                //4.兄弟节点左右节点都有
                {
                    //兄弟黑,必有俩红
                    if (brother->color == 0)
                    {
                        brother->left->color = parent->color;
                    }else{
                    //兄弟红,必有俩黑,且父亲必黑
                        brother->color       = 0;
                        brother->right->color = 1;
                    }
                    parent = right_rotation(parent);
                    return head_flag ? parent : head;
                }
            }
        }else if (root->left != NULL && root->right == NULL)
        //只有一个左节点 那么依据红黑树特性,root必然为黑色,且左节点为红色
        {
            root->left->color = 0;
            //被删除节点在左树上
            if (root == parent->left)
            {
                parent->left       = root->left;
                root->left->parent = parent;
            }else
            //被删除节点在右子树上
            {
                parent->right      = root->left;
                root->left->parent = parent;
            }
            root = NULL;
            free(root);
            return head;
        }else if (root->left == NULL && root->right != NULL)
        //只有一个右节点 那么依据红黑树特性,root必然为黑色,且右节点为红色
        {
            printf("root null:\n");
            root->right->color = 0;

            //被删除节点在左树上
            if (root == parent->left)
            {
                parent->left       = root->right;
                root->right->parent= parent;
            }else
            //被删除节点在右子树上
            {
                parent->right      = root->right;
                root->right->parent= parent;
            }
            root = NULL;
            free(root);
            return head;
        }else
        //左右节点都在,选一个前驱或者后继
        //后继节点:删除节点的右子树中的最小节点,即右子树中最左节点。
        //前驱节点:删除节点的左子树中最大节点,即左子树中最右节点。
        //这里使用后继节点作为代替结点
        {
            RBTreeNode *min = FindMin(root->right);
            root->data = min->data;
            return Delete(head, min, min->data);
        }
    }
    return head;
}

4 测试代码

4.1 前序遍历 用于验证

//前序遍历
void preOrderTraverse(RBTreeNode *root)
{
    if (root != NULL)
    {        
        if (root->parent != NULL)
        {
            printf("%d color: %d parent:%d\n", root->data, root->color, root->parent->data);
        }else{
            printf("%d color: %d\n", root->data, root->color);
        }
        preOrderTraverse(root->left);
        preOrderTraverse(root->right);
    }
}

4.2 测试代码

场景较多 这里只给出一个示例

int main(int argc, char const *argv[])
{
    struct RBTreeNode *node;

    //1.1 测试用例
    node = NULL;
    node = inserRB(node, 8, NULL);
    node = inserRB(node, 4, NULL);
    node = inserRB(node, 12, NULL);
    node = inserRB(node, 2, NULL);
    //node = inserRB(node, 20, NULL);
    //node = inserRB(node, 30, NULL);
    //node = inserRB(node, 14, NULL);
    //node = inser(node, 4, NULL);
    printf("***1测试用例 前序***:  \n");
    preOrderTraverse(node);

    //node = Delete(node,node,30);
    node = Delete(node,node,8);

    printf("***测试用例 前序***:  \n");
    preOrderTraverse(node);
    return 0;
}

Done!

5 思考

常见的场景:这里会对8递归
                Parent            Parent
                   |                 |
                8(黑)              8(红)
                 /  \               / \
              4(红)  10(红)  =>  4(黑) 10(黑)   => reblace(8)
               /                  /
            2(红)              2(红)
            
考虑一个场景:Parent是红色,例子如下,新插入4

                20(黑)              20(黑)
                /   \              /   \
            10(红)  30(黑)       10(红)  30(黑)
             /  \                / \  
          8(黑) 12(黑)         8(红) 12(黑)  => reblace(8) 是不是就是新插入节点是红色,父红,叔叔黑
           /  \               /  \
       6(红)  9(红)   =>   6(黑)   9(黑)
       /                   /
     4(红) [新增]        4(红)            

实例: image.png