• 红黑树的原理与实现(C语言)


    红黑树定义和性质

    红黑树是一种含有红黑结点并能自平衡的二叉查找树。它必须满足下面性质:

    • 性质1:每个节点要么是黑色,要么是红色。
    • 性质2:根节点是黑色。
    • 性质3:每个叶子节点(NIL)是黑色。(这里的叶子节点是指空叶子节点)
    • 性质4:每个红色结点的两个子结点一定都是黑色。
    • 性质5:任意一结点到每个叶子结点的路径都包含数量相同的黑结点。


    红黑树是一棵近似平衡的二叉搜索树,它查找、插入和删除的时间复杂度都是O(lg n);

    红黑树的自平衡

    插入和删除会破坏红黑树的平衡,需要靠三种操作来维持:左旋、右旋和变色。旋转的时候维持二叉搜索树的性质就好(左子结点比根节点小,右子节点比根节点大

    左旋和右旋的意义就是让违反性质4的节点不断上移到根节点,然后让根节点变成黑色(避免违反性质5),这样就可以达到自平衡

    • 左旋:对x进行左旋,意味着,将“x的右孩子”设为“x的父亲节点”;即,将 x变成了一个左节点(x成了为z的左孩子)!。 因此,左旋中的“左”,意味着“被旋转的节点将变成一个左节点”

      左旋示例图(以x为节点进行左旋):

                                   z
       x                          /                  
      /       --(左旋)-->       x
     y   z                      /
                               y

       

    /*
     * 左旋示意图(对节点x进行左旋):
     *
     *
     *      px                              px
     *     /                               /
     *    x                               y
     *   /        --(左旋)-->           /                 #
     *  lx   y                          x  ry
     *     /                          /  
     *    ly   ry                     lx  ly
     *
     * 三处变化:
     * 1、被旋转的节点  变成  左节点
     * 2、被旋转节点的右节点  变成  根节点
     * 3、被旋转节点的右节点的左节点  变成  被旋转节点的右节点
     *
     * 代码实现的时候,修改这种变化从第3点开始修改(也就是最下面的变化开始往上修改)
     */
    
     //以x为支点进行左旋
    void LeftRotate(Tree* tree, node* x)
    {
        //新建节点y,保存x的右节点
        node* y = x->right;
    
        //将被旋转节点的右节点的左节点  设置为  被旋转节点的右节点
        x->right = y->left;
    
        //更新被旋转节点x的  右节点的左节点  的父节点
        if (y->left != NULL)
            y->left->parent = x;
    
        //更新旋转后  y的父节点
        y->parent = x->parent;
    
        //判断x为根节点的情况,那么旋转后y变成根节点
        if (x->parent == NULL)
            tree->root = y;
        else
        {
            //如果x是它父节点的左儿子,旋转后这个父节点的左儿子就是y
            if (x == x->parent->left)
                x->parent->left = y;
            else
                x->parent->right = y;
        }
    
        y->left = x;
        x->parent = y;
    
    }
    左旋(LeftRotate)
    • 右旋:对x进行右旋,意味着,将“x的左孩子”设为“x的父亲节点”;即,将 x变成了一个右节点(x成了为y的右孩子)! 因此,右旋中的“右”,意味着“被旋转的节点将变成一个右节点”

      右旋示例图(以x为节点进行右旋):

                                 y
       x                                             
      /       --(右旋)-->           x
     y   z                            
                                       z

      

    /*
     * 右旋示意图(对节点y进行左旋):
     *
     *
     *            py                               py
     *           /                                /
     *          y                                x
     *         /        --(右旋)-->            /                       #
     *        x   ry                           lx   y
     *       /                                    /                    #
     *      lx  rx                                rx  ry
     *
     * 三处变化:
     * 1、被旋转的节点  变成  右节点
     * 2、被旋转节点的左节点  变成  根节点
     * 3、被旋转节点的左节点的右节点  变成  被旋转节点的左节点
     */
    
     //以y为支点进行左旋
    void RightRotate(Tree* tree, node* y)
    {
        //新建x节点保存被旋转节点的左节点
        node* x = y->left;
    
        //将被旋转节点的左节点的右节点  设置为  被旋转节点的左节点
        y->left = x->right;
    
        //更新被旋转节点y的  左节点的右节点  的父节点
        if (x->right != NULL)
            x->right->parent = y;
    
        //更新旋转后  x的父节点
        x->parent = y->parent;
    
        //判断y为根节点的情况,那么旋转后x变成根节点
        if (y->parent == NULL)
            tree->root = x;
        else
        {
            //如果被旋转节点y是它父节点的左儿子,旋转后这个父节点的左儿子就是x
            if (y == y->parent->right)
                y->parent->right = x;
            else
                y->parent->left = x;
        }
    
        x->right = y;
        y->parent = x;
    
    }
    右旋(RightRotate)
    • 变色:结点的颜色由红变黑或由黑变红。



     

    红黑树的插入

    红黑树的插入分两部分,分别是插入和自平衡

    插入节点

         插入的时候和二叉搜索树一样分两部分

         1、确定插入位置是在那个叶子节点下面,即找到插入节点位置的父亲节点parent

         2、新建一个节点new,同时描述父子节点关系:new是parent的左(右)子节点,parent是new的父亲节点

         3、将插入的节点着色为"红色"

         4、调用自平衡函数fixup()

    代码

    void insert(Tree* tree, node* New)
    {
        //新建一个节点,暂时保存父节点
        node* temp_parent = NULL;
        //根节点
        node* root = tree->root;
    
        while (root != NULL)
        {
            temp_parent = root;
            if (New->data < root->data)
                root = root->left;
            else
                root = root->right;
        }
    
        //找到之后,给新节点new构建关系
        //可以先确定的是,插入新节点new的父节点一定是temp_parent
        New->parent = temp_parent;
    
        //但是还不确定new是temp_parent的左节点还是右节点
        if (temp_parent != NULL)
        {
            if (New->data < temp_parent->data)
                temp_parent->left = New;
            else
                temp_parent->right = New;
        }
        else
        {
            //父节点为空,说明树为空,则插入的节点就是根节点
            tree->root = New;
        }
        //插入的节点颜色为红
        New->color = red;
    
        //自平衡修正颜色
        insert_fixup(tree, New);
    
    }
    插入(insert)

    自平衡

        自平衡的作用就是维持红黑树以下的性质:

    (1) 每个节点或者是黑色,或者是红色。
    (2) 根节点是黑色。
    (3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]
    (4) 如果一个节点是红色的,则它的子节点必须是黑色的。
    (5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

     

    我们仔细分析之后,对于插入一个红色节点(插入节点默认为红色),只可能违背性质4,所以自平衡的时候要想办法满足性质4即可

     

     

    根据被插入节点的父节点的情况,可以将"当节点z被着色为红色节点,并插入二叉树"划分为三种情况来处理。
    ① 情况说明:被插入的节点是根节点。
        处理方法:直接把此节点涂为黑色。
    ② 情况说明:被插入的节点的父节点是黑色。
        处理方法:什么也不需要做。节点被插入后,仍然是红黑树。
    ③ 情况说明:被插入的节点的父节点是红色。
        处理方法:那么,该情况与红黑树的“特性(5)”相冲突。这种情况下,被插入节点是一定存在非空祖父节点的;进一步的讲,被插入节点也一定存在叔叔节点(即使叔叔节点为空,我们也视之为存在,空节点本身就是黑色节点)。理解这点之后,我们依据"叔叔节点的情况",将这种情况进一步划分为3种情况(Case)。

     

    上面三种情况(Case)处理问题的核心思路都是:将红色的节点移到根节点(就是不断把当前节点上移);然后,将根节点设为黑色。

    通过左旋或右旋可以达到将红色节点上移的目的:

    1、如果红色节点是父亲节点的左孩子,右旋

    2、如果红色节点是父亲节点的右孩子,左旋

    伪代码

    插入的节点一定是叶子节点,我们只需要往上修正(即只每次修正只考虑他的父亲和祖父节点)
    而修正的核心就是:把红色节点上移到根节点,然后把根节点设置为黑色
    now:当前节点
    parent:now的父亲节点
    uncle:now的叔叔节点
    gparent:now的祖父节点
    
    while(当前节点是红色并且不是根节点)
    {
        如果parent是红色(黑色不需要修正)
        {    
            如果parent是gparent的左儿子:
            {   
                Case 1:now是红色,parent是红色,uncle是红色
                    策略:
                        a、parent和uncle变黑,gparent变红
                        b、把gparent设为当前节点进行下一次修正
                
                Case 2:now是parent的左儿子,parent是红,uncle是黑:
                    策略:
                        a、以parent节点为支点进行左旋
    
                Case 3:now是parent的右儿子,parent是红,uncle是黑:
                    策略:
                        a、parent变黑,gparent变红
                        b、以gparent为支点进行右旋
                       
            }
            如果parent是gparent的右儿子:
            {
                Case 1:now是红色,parent是红色,uncle是红色
                    策略:
                        a、parent和uncle变黑,gparent变红
                        b、把gparent设为当前节点进行下一次修正
                
                Case 2:now是parent的左儿子,parent是红,uncle是黑:
                    策略:
                        a、以parent节点为支点进行右旋
    
                Case 3:now是parent的右儿子,parent是红,uncle是黑:
                    策略:
                        a、parent变黑,gparent变红
                        b、以gparent为支点进行左旋
            }
        }
    }
    
    now是根节点:
        直接把根节点变黑

    代码

    插入修正(insert_fixup)

    红黑树的删除

    将红黑树内的某一个节点删除。需要执行的操作依次是:首先,将红黑树当作一颗二叉查找树,将该节点从二叉查找树中删除;然后,通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。详细描述如下:

    第一步:将红黑树当作一颗二叉查找树,将节点删除。
           这和"删除常规二叉查找树中删除节点的方法是一样的"。分3种情况:
    ① 被删除节点没有儿子,即为叶节点。那么,直接将该节点删除就OK了。
    ② 被删除节点只有一个儿子。那么,直接删除该节点,并用该节点的唯一子节点顶替它的位置。
    ③ 被删除节点有两个儿子。那么,先找出它的后继节点;然后把“它的后继节点的内容”复制给“该节点的内容”;之后,删除“它的后继节点”。在这里,后继节点相当于替身,在将后继节点的内容复制给"被删除节点"之后,再将后继节点删除。这样就巧妙的将问题转换为"删除后继节点"的情况了,下面就考虑后继节点(replce)。

    情况一: target     情况二: target
         /                     /     
          A      B           A        B
           /               /     
       replace    C          replace     C
                     
        NULL            child


    后继节点是右子树中最小的节点,所以replace不可能有左孩子,那么意味着"该节点的后继节点"要么没有儿子,要么只有一个儿子。若没有儿子,则按"情况① "进行处理;若只有一个儿子,则按"情况② "进行处理。

    代码

    void delect(Tree* tree, node* target)
    {
        node* parent, *child;
        int color;
    
        //既有左子树,又有右子树,在右子树中找后继节点
        if (target->left != NULL && target->right != NULL)
        {
            //找到后继节点(替代节点),用后继节点替换被删除的节点位置
            //然后在删除后继节点的位置
            node* replace = target;
            replace = target->right;
            while (replace->left != NULL)
                replace = replace->left;
    
            //被删除的节点不是根节点(根节点没有父亲节点)
            if (target->parent != NULL)
            {
                //这里的替换不仅仅是值得替换,还有关系上得替换
                if (target->parent->left == target)
                    target->parent->left = replace;
                else
                    target->parent->right = replace;
            }
            else
                tree->root = replace;
    
            //替换完成之后,删除target的问题就转换成删除后继节点的问题
            //现在考虑这个后继节点(replace):
            //replace节点是target右子树中最小的节点,
            //所以replace要么没有儿子节点,要么只有右儿子节点
            child = replace->right;
            parent = replace->parent;
    
            //保存后继节点的颜色
            color = replace->color;
    
            if (target == parent)
            {
                /*           target
                *             /  
                *            A  replace
                *
                *///这种情况的话先用replace替换target,然后直接删除replace,
                //只需要更新父亲关系,不用更新replace的儿子的关系(replace没有儿子)
                parent = replace;
            }
            else
            {
                /*情况一:         target          情况二       target
                *                 /                            /    
                *                A      B                      A      B
                *                      /                           /  
                *                replace   C                   replace  C
                *                                                
                *                    NULL                        child
                *
                */
                if (child != NULL)
                {
                    //情况一:直接删除replace,不用调整replace的父子关系
                    child->parent = parent;
                }
    
                //情况二:删除replace,它的后继节点就是child,接下来调整父子关系
    
                parent->left = child;
                replace->right = target->right;
                target->right->parent = replace;
            }
    
            //replace替代target之后,调整父子关系
            replace->parent = target->parent;
            replace->left = target->left;
            replace->color = target->color;
            target->left->parent = replace;
    
            //自平衡:颜色修正
            //如果replace的颜色是红色,把他替换到target位置,replace节点所在支路的黑色节点数量不变
            //如果replace的颜色是黑色,把他替换到target位置,replace节点所在支路的黑色节点数量减一
            //这样违反性质5,又需要继续调整
            if (color == black)
                delect_fixup(tree, child, parent);
            free(target);
            return;
        }
        //只有左子树或右子树,它的左儿子或右儿子就是后继节点
        //child就是replace
        if (target->left != NULL)
            child = target->left;
        else
            child = target->right;
    
        //确定后继节点之后,开始调整父子关系
        parent = target->parent;
        color = target->color;
    
        //target为叶子节点
        if (child != NULL)
            child->parent = target;
    
        //target不为根节点
        if (parent != NULL)
        {
            if (parent->left == target)
                parent->left = child;
            else
                parent->right = child;
        }
        //target为根节点
        else
            tree->root = child;
    
        //如果target的颜色是黑色的话,破坏性质5
        if (color == black)
            delect_fixup(tree, child, parent);
        free(target);
    }
    删除(delect)

    第二步:通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。

    在分析之前,我们再次温习一下红黑树的几个特性:
    (1) 每个节点或者是黑色,或者是红色。
    (2) 根节点是黑色。
    (3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]
    (4) 如果一个节点是红色的,则它的子节点必须是黑色的。
    (5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

    在第一步中用replace去替换target节点,然后删除replace节点。如果replace节点是红色,A ->B ->replace这条支路的黑色节点个数不变,但是如果target->parent是红色,会违反性质4;

    如果replace是黑色节点 ,A ->B ->replace这条支路的黑色节点个数会减少(当然,也可能增加),会违反性质5;

    如果target是根节点,replace是红色,替换之后会违反性质2。

    所以这个时候需要修正红黑树

    第一步,先解决性质5的问题:

    这里先弄清楚,性质5被破坏是因为target节点被删除引起的,

    我们假设"x包含一个额外的黑色"(x原本的颜色还存在),这样就不会违反"特性(5)"。为什么呢?

    我们知道:删除节点y之后,x占据了原来节点y的位置。 既然删除y(y是黑色),意味着减少一个黑色节点;那么,再在该位置上增加一个黑色即可。这样,当我们假设"x包含一个额外的黑色",就正好弥补了"删除y所丢失的黑色节点",也就不会违反"特性(5)"。 因此,假设"x包含一个额外的黑色"(x原本的颜色还存在),这样就不会违反"特性(5)"。(附加节点只对右子树有效)

    第二步,上一步解决了性质5的问题,但是又引入性质1的问题,即我们需要解决性质1、2、4的问题

    我们用delect_fixup()函数解决,它的核心思想是:将x所包含的额外的黑色不断沿树上移(向根方向移动),直到出现下面的姿态:

    ① 情况说明:x是“红+黑”节点。
        处理方法:直接把x设为黑色,结束。此时红黑树性质全部恢复。
    ② 情况说明:x是“黑+黑”节点,且x是根。
        处理方法:什么都不做,结束。此时红黑树性质全部恢复。
    ③ 情况说明:x是“黑+黑”节点,且x不是根。
        处理方法:这种情况又可以划分为4种子情况。这4种子情况如下表所示:

    伪代码:

    删除的节点(target)可能是任意节点,但是我们使用后继节点(replace)替换之后变成删除叶子节点
    但是红黑树的性质被破坏还是因为target节点被替换
    
    now:当前节点
    parent:now的父亲节点
    brothe:now的兄弟节点
    gparent:now的祖父节点
    
    while(当前节点不是根节点并且为黑色(空节点也认为是黑色))
    {
        now是parent的左儿子:
        {
            Case 1:now颜色是黑+黑,brothe是红色
                策略:
                    a、brothe变黑,father变红
                    b、以father为支点进行左旋
                    c、左旋后重新设置now的兄弟节点
    
            Case 2:now颜色是黑+黑,brothe是黑,brothe的左右儿子都是黑(NULL也是黑)
                策略:
                    a、brothe变红
                    b、father设置为新的now节点
    
            Case 3:now颜色是黑+黑,brothe是黑,brothe的左儿子是红,右儿子是黑
                策略:
                    a、将brothe点变红,brothe的左儿子变黑
                    b、以brothe为支点进行右旋
                    c、右旋后,重新设置now的兄弟节点
            Case 4:now颜色是黑+黑,brothe是黑,brothe的左儿子是任意颜色,右儿子是黑
                策略:(核心是把多余的黑色节点去掉(上升到根节点,然后把根节点设置为黑色))
                    a、将parent的颜色给brothe,将parent变黑,brothe的右儿子节点变黑
                    b、以parent为支点进行左旋
                    c、设置now为根节点
        }
        now是parent的右儿子
        {
             Case 1:now颜色是红+黑,brothe是红色
                策略:
                    a、brothe变黑,father变红
                    b、以father为支点进行右旋
                    c、右旋后重新设置now的兄弟节点
    
            Case 2:now颜色是红+黑,brothe是黑,brothe的左右儿子都是黑(NULL也是黑)
                策略:
                    a、brothe变红
                    b、father设置为新的now节点
    
            Case 3:now颜色是红+黑,brothe是黑,brothe的左儿子是红,右儿子是黑
                策略:
                    a、将brothe点变红,brothe的右儿子变黑
                    b、以brothe为支点进行左旋
                    c、左旋后,重新设置now的兄弟节点
            Case 4:now颜色是红+黑,brothe是黑,brothe的左儿子是任意颜色,右儿子是红
                策略:(核心是把多余的黑色节点去掉(上升到根节点,然后把根节点设置为黑色))
                    a、将parent的颜色给brothe,将parent变黑,brothe的右儿子节点变黑
                    b、以parent为支点进行右旋
                    c、设置now为根节点
    
        }
    }
    
    now不为空:
        直接把根节点变黑

     代码

    void delect_fixup(Tree* tree, node* now, node* parent)
    {
        //now是insert中的替代节点replace
        //now的兄弟节点
        node* brothe;
    
        //当前节点不是根节点并且为黑色(空节点也认为是黑色)
        while ((now == NULL || now->color == black) && now != tree->root)
        {
            if (parent->left == now)
            {
                brothe = parent->right;
                if (brothe->color == red)
                {
                    //Case 1:当前节点是黑+黑,并且兄弟节点是红色
                    //策略:
                    //1、把兄弟节点变黑,父亲节点变红
                    //2、以父亲为支点进行左旋
                    //3、左旋后重新设置now的兄弟节点
                    brothe->color = black;
                    parent->color = red;
                    LeftRotate(tree, parent);
                    brothe = parent->right;
                }
    
                if ((brothe->left == NULL || brothe->left->color == black) &&
                    (brothe->right == NULL || brothe->right->color == black))
                {
                    //Case 2:当前节点是黑+黑,兄弟节点是黑,兄弟节点的左右儿子都是黑(NULL也是黑)
                    //策略:
                    //1、把兄弟节点变红
                    //2、now->parent设置为新的now节点
                    brothe->color = red;
                    now = now->parent;
                    parent = now->parent;
                }
                else
                {
                    if (brothe->right == NULL || brothe->right->color == black)
                    {
                        //Case 3:当前节点是黑+黑,兄弟节点是黑,兄弟节点的右儿子是黑,左儿子是红
                        //策略:
                        //1、将兄弟节点变红,兄弟节点的左儿子变黑
                        //2、以兄弟节点为支点进行右旋
                        //3、右旋后,重新设置now的兄弟节点
                        brothe->left->color = black;
                        brothe->color = red;
                        RightRotate(tree, brothe);
                        brothe = parent->right;
                    }
    
                    //Case 4:当前节点是黑+黑,兄弟节点是黑,兄弟节点的右儿子是红,左儿子是任意颜色
                    //策略:(核心是把多余的黑色节点去掉(上升到根节点,然后把根节点设置为黑色))
                    //1、将父亲节点的颜色给兄弟节点,将父亲节点变黑,兄弟节点的右儿子节点变黑
                    //2、以父亲节点为支点进行左旋
                    //3、设置now为根节点
                    brothe->color = parent->color;
                    parent->color = black;
                    brothe->right->color = black;
                    LeftRotate(tree, parent);
                    now = tree->root;
                    break;
    
                }
            }
            else
            {
                brothe = parent->left;
                if (brothe->color == red)
                {
                    //Case 1:当前节点是红+黑,并且兄弟节点是红色
                    //策略:
                    //1、把兄弟节点变黑,父亲节点变红
                    //2、以父亲为支点进行右旋
                    //3、右旋后重新设置now的兄弟节点
                    brothe->color = black;
                    parent->color = red;
                    RightRotate(tree, parent);
                    brothe = parent->left;
                }
    
                if ((brothe->left == NULL || brothe->left->color == black) &&
                    (brothe->right == NULL || brothe->right->color == black))
                {
                    //Case 2:当前节点是红+黑,兄弟节点是黑,兄弟节点的左右儿子都是黑(NULL也是黑)
                    //策略:
                    //1、把兄弟节点变红
                    //2、now->parent设置为新的now节点
                    brothe->color = red;
                    now = now->parent;
                    parent = now->parent;
                }
                else
                {
                    if (brothe->left == NULL || brothe->left->color == black)
                    {
                        //Case 3:当前节点是红+黑,兄弟节点是黑,兄弟节点的右儿子是黑,左儿子是红
                        //策略:
                        //1、将兄弟节点变红,兄弟节点的右儿子变黑
                        //2、以兄弟节点为支点进行左旋
                        //3、左旋后,重新设置now的兄弟节点
                        brothe->right->color = black;
                        brothe->color = red;
                        LeftRotate(tree, brothe);
                        brothe = parent->left;
                    }
    
                    //Case 4:当前节点是红+黑,兄弟节点是黑,兄弟节点的右儿子是红,左儿子是任意颜色
                    //策略:(核心是把多余的黑色节点去掉(上升到根节点,然后把根节点设置为黑色))
                    //1、将父亲节点的颜色给兄弟节点,将父亲节点变黑,兄弟节点的右儿子节点变黑
                    //2、以父亲节点为支点进行右旋
                    //3、设置now为根节点
                    brothe->color = parent->color;
                    parent->color = black;
                    brothe->left->color = black;
                    RightRotate(tree, parent);
                    now = tree->root;
                    break;
    
                }
            }
    
        }
        if (now != NULL)
            now->color = black;
    
    }
    删除修正(delect_fixup)

    红黑树的查找

    node* search(Tree* tree,int x)
    {
        node* root=tree->root;
        while(root!=NULL&&root->data!=x)
        {
            if(x<root->data)
                root=root->left;
            else
                root=root->right;
        }
        return root;
    }

    红黑树的完整代码(C语言实现)

    #include<iostream>
    #include<stdlib.h>
    #include<string.h>
    #define red 0
    #define black 1
    using namespace std;
    
    struct node//节点的结构
    {
        int data;
        char color;
        struct node* parent;
        struct node* left;
        struct node* right;
    };
    struct Tree//树的结构
    {
        node* root;
    };
    //创建一个节点
    node* Creat_Node(int data, int color, node* left, node* right, node* parent)
    {
        node* New = new node;
        New->data = data;
        New->color = color;
        New->left = left;
        New->right = right;
        New->parent = parent;
        return New;
    }
    
    /*
     * 左旋示意图(对节点x进行左旋):
     *
     *
     *      px                              px
     *     /                               /
     *    x                               y
     *   /        --(左旋)-->           /                 #
     *  lx   y                          x  ry
     *     /                          /  
     *    ly   ry                     lx  ly
     *
     * 三处变化:
     * 1、被旋转的节点  变成  左节点
     * 2、被旋转节点的右节点  变成  根节点
     * 3、被旋转节点的右节点的左节点  变成  被旋转节点的右节点
     *
     * 代码实现的时候,修改这种变化从第3点开始修改(也就是最下面的变化开始往上修改)
     */
    
     //以x为支点进行左旋
    void LeftRotate(Tree* tree, node* x)
    {
        //新建节点y,保存x的右节点
        node* y = x->right;
    
        //将被旋转节点的右节点的左节点  设置为  被旋转节点的右节点
        x->right = y->left;
    
        //更新被旋转节点x的  右节点的左节点  的父节点
        if (y->left != NULL)
            y->left->parent = x;
    
        //更新旋转后  y的父节点
        y->parent = x->parent;
    
        //判断x为根节点的情况,那么旋转后y变成根节点
        if (x->parent == NULL)
            tree->root = y;
        else
        {
            //如果x是它父节点的左儿子,旋转后这个父节点的左儿子就是y
            if (x == x->parent->left)
                x->parent->left = y;
            else
                x->parent->right = y;
        }
    
        y->left = x;
        x->parent = y;
    
    }
    
    /*
     * 右旋示意图(对节点y进行左旋):
     *
     *
     *            py                               py
     *           /                                /
     *          y                                x
     *         /        --(右旋)-->            /                       #
     *        x   ry                           lx   y
     *       /                                    /                    #
     *      lx  rx                                rx  ry
     *
     * 三处变化:
     * 1、被旋转的节点  变成  右节点
     * 2、被旋转节点的左节点  变成  根节点
     * 3、被旋转节点的左节点的右节点  变成  被旋转节点的左节点
     */
    
     //以y为支点进行左旋
    void RightRotate(Tree* tree, node* y)
    {
        //新建x节点保存被旋转节点的左节点
        node* x = y->left;
    
        //将被旋转节点的左节点的右节点  设置为  被旋转节点的左节点
        y->left = x->right;
    
        //更新被旋转节点y的  左节点的右节点  的父节点
        if (x->right != NULL)
            x->right->parent = y;
    
        //更新旋转后  x的父节点
        x->parent = y->parent;
    
        //判断y为根节点的情况,那么旋转后x变成根节点
        if (y->parent == NULL)
            tree->root = x;
        else
        {
            //如果被旋转节点y是它父节点的左儿子,旋转后这个父节点的左儿子就是x
            if (y == y->parent->right)
                y->parent->right = x;
            else
                y->parent->left = x;
        }
    
        x->right = y;
        y->parent = x;
    
    }
    
    void insert_fixup(Tree* tree, node* now)
    {
        node* parent, *gparent;
    
        //只有被插入节点有父亲节点,并且父亲节点是红色才需要修正
        while ((parent = now->parent) && parent->color == red)
        {
            gparent = parent->parent;
    
            //判断父亲节点是祖父节点的左儿子还是右儿子
            if (parent == gparent->left)
            {
                node* uncle = gparent->right;
                if (uncle&&uncle->color == red)
                {
                    //case 1:当前节点是红色,它的父亲节点是红色,叔叔节点也是红色
    
                    //处理措施:
                    //将父亲节点和叔叔节点都变成黑色,祖父节点变成红色
                    //并且把祖父节点设为 当前节点
                    parent->color = black;
                    uncle->color = black;
                    gparent->color = red;
                    now = gparent;
                    continue;
                }
    
                //case 2:当前节点是父亲节点的右孩子,父亲节点是红色,并且叔叔节点是黑色
    
                //处理措施:
                //以父亲节点为支点进行左旋
                //把父亲节点作为当前节点,更新父子关系
                if (parent->right == now)
                {
                    node* temp;
                    LeftRotate(tree, parent);
                    /*  左旋前:       gparent            以parent为支点左旋后:  gparent
                    *                   /                                         /   
                    *               parent   uncle                               now   uncle
                    *               /                                          /
                    *           brothe   now                                parent
                    *                                                         /
                    *                                                      brothe
                    */
                    //以父亲节点parent作为当前节点进行左旋之后,parent节点下移成为now的左儿子节点
                    //now节点成为parent节点的父亲节点
                    //所以这里需要交换,更新谁是父亲节点,谁是当前节点
                    temp = parent;
                    parent = now;
                    now = temp;
                }
                //case 3:当前节点是左儿子,它的父亲节点是红色,叔叔节点是黑色
    
               //处理措施:
               //将父亲节点设置为黑色
               //祖父节点设置为红色
               //以祖父节点为支点进行右旋
                parent->color = black;
                gparent->color = red;
                RightRotate(tree, gparent);
    
                //这里父子关系没有变化,所以不用更新
    
            }
            else//父亲节点是祖父节点的右儿子
            {
                node* uncle = gparent->left;
                if (uncle&&uncle->color == red)
                {
                    //case 1:当前节点是红色,它的父亲节点是红色,叔叔节点也是红色
    
                    //处理措施:
                    //将父亲节点和叔叔节点都变成黑色,祖父节点变成红色
                    //并且把祖父节点设为 当前节点
                    parent->color = black;
                    uncle->color = black;
                    gparent->color = red;
                    now = gparent;
                    continue;
                }
    
                //case 2:当前节点是父亲节点的左孩子,父亲节点是红色,并且叔叔节点是黑色
    
                //处理措施:
                //以父亲节点为支点进行右旋
                //把父亲节点作为当前节点,更新父子关系
                if (parent->left == now)
                {
                    /*  右旋前:       gparent            以parent为支点右旋后:  gparent
                    *                   /                                         /   
                    *               parent   uncle                              now   uncle
                    *               /                                            
                    *           now   brothe                                     parent
                    *                                                               
                    *                                                               brothe
                    */
                    //以父亲节点parent作为当前节点进右旋之后,parent节点下移成为now的右儿子节点
                    //now节点成为parent节点的父亲节点
                    //所以这里需要交换,更新谁是父亲节点,谁是当前节点
                    node* temp;
                    RightRotate(tree, parent);
                    temp = parent;
                    parent = now;
                    now = temp;
                }
                //case 3:当前节点是父亲节点的右儿子,它的父亲节点是红色,叔叔节点是黑色
    
               //处理措施:
               //将父亲节点设置为黑色
               //祖父节点设置为红色
               //以祖父节点为支点进行左旋
                parent->color = black;
                gparent->color = red;
                LeftRotate(tree, gparent);
    
                //这里父子关系没有变化,所以不用更新
            }
    
        }
    
        //自平衡的核心就是把红色节点上移到根节点,然后把根节点设置为黑色
        tree->root->color = black;
    }
    
    void insert(Tree* tree, node* New)
    {
        //新建一个节点,暂时保存父节点
        node* temp_parent = NULL;
        //根节点
        node* root = tree->root;
    
        while (root != NULL)
        {
            temp_parent = root;
            if (New->data < root->data)
                root = root->left;
            else
                root = root->right;
        }
    
        //找到之后,给新节点new构建关系
        //可以先确定的是,插入新节点new的父节点一定是temp_parent
        New->parent = temp_parent;
    
        //但是还不确定new是temp_parent的左节点还是右节点
        if (temp_parent != NULL)
        {
            if (New->data < temp_parent->data)
                temp_parent->left = New;
            else
                temp_parent->right = New;
        }
        else
        {
            //父节点为空,说明树为空,则插入的节点就是根节点
            tree->root = New;
        }
        //插入的节点颜色为红
        New->color = red;
    
        //自平衡修正颜色
        insert_fixup(tree, New);
    
    }
    
    void delect_fixup(Tree* tree, node* now, node* parent)
    {
        //now是insert中的替代节点replace
        //now的兄弟节点
        node* brothe;
    
        //当前节点不是根节点并且为黑色(空节点也认为是黑色)
        while ((now == NULL || now->color == black) && now != tree->root)
        {
            if (parent->left == now)
            {
                brothe = parent->right;
                if (brothe->color == red)
                {
                    //Case 1:当前节点是黑+黑,并且兄弟节点是红色
                    //策略:
                    //1、把兄弟节点变黑,父亲节点变红
                    //2、以父亲为支点进行左旋
                    //3、左旋后重新设置now的兄弟节点
                    brothe->color = black;
                    parent->color = red;
                    LeftRotate(tree, parent);
                    brothe = parent->right;
                }
    
                if ((brothe->left == NULL || brothe->left->color == black) &&
                    (brothe->right == NULL || brothe->right->color == black))
                {
                    //Case 2:当前节点是黑+黑,兄弟节点是黑,兄弟节点的左右儿子都是黑(NULL也是黑)
                    //策略:
                    //1、把兄弟节点变红
                    //2、now->parent设置为新的now节点
                    brothe->color = red;
                    now = now->parent;
                    parent = now->parent;
                }
                else
                {
                    if (brothe->right == NULL || brothe->right->color == black)
                    {
                        //Case 3:当前节点是黑+黑,兄弟节点是黑,兄弟节点的右儿子是黑,左儿子是红
                        //策略:
                        //1、将兄弟节点变红,兄弟节点的左儿子变黑
                        //2、以兄弟节点为支点进行右旋
                        //3、右旋后,重新设置now的兄弟节点
                        brothe->left->color = black;
                        brothe->color = red;
                        RightRotate(tree, brothe);
                        brothe = parent->right;
                    }
    
                    //Case 4:当前节点是黑+黑,兄弟节点是黑,兄弟节点的右儿子是红,左儿子是任意颜色
                    //策略:(核心是把多余的黑色节点去掉(上升到根节点,然后把根节点设置为黑色))
                    //1、将父亲节点的颜色给兄弟节点,将父亲节点变黑,兄弟节点的右儿子节点变黑
                    //2、以父亲节点为支点进行左旋
                    //3、设置now为根节点
                    brothe->color = parent->color;
                    parent->color = black;
                    brothe->right->color = black;
                    LeftRotate(tree, parent);
                    now = tree->root;
                    break;
    
                }
            }
            else
            {
                brothe = parent->left;
                if (brothe->color == red)
                {
                    //Case 1:当前节点是红+黑,并且兄弟节点是红色
                    //策略:
                    //1、把兄弟节点变黑,父亲节点变红
                    //2、以父亲为支点进行右旋
                    //3、右旋后重新设置now的兄弟节点
                    brothe->color = black;
                    parent->color = red;
                    RightRotate(tree, parent);
                    brothe = parent->left;
                }
    
                if ((brothe->left == NULL || brothe->left->color == black) &&
                    (brothe->right == NULL || brothe->right->color == black))
                {
                    //Case 2:当前节点是红+黑,兄弟节点是黑,兄弟节点的左右儿子都是黑(NULL也是黑)
                    //策略:
                    //1、把兄弟节点变红
                    //2、now->parent设置为新的now节点
                    brothe->color = red;
                    now = now->parent;
                    parent = now->parent;
                }
                else
                {
                    if (brothe->left == NULL || brothe->left->color == black)
                    {
                        //Case 3:当前节点是红+黑,兄弟节点是黑,兄弟节点的右儿子是黑,左儿子是红
                        //策略:
                        //1、将兄弟节点变红,兄弟节点的右儿子变黑
                        //2、以兄弟节点为支点进行左旋
                        //3、左旋后,重新设置now的兄弟节点
                        brothe->right->color = black;
                        brothe->color = red;
                        LeftRotate(tree, brothe);
                        brothe = parent->left;
                    }
    
                    //Case 4:当前节点是红+黑,兄弟节点是黑,兄弟节点的右儿子是红,左儿子是任意颜色
                    //策略:(核心是把多余的黑色节点去掉(上升到根节点,然后把根节点设置为黑色))
                    //1、将父亲节点的颜色给兄弟节点,将父亲节点变黑,兄弟节点的右儿子节点变黑
                    //2、以父亲节点为支点进行右旋
                    //3、设置now为根节点
                    brothe->color = parent->color;
                    parent->color = black;
                    brothe->left->color = black;
                    RightRotate(tree, parent);
                    now = tree->root;
                    break;
    
                }
            }
    
        }
        if (now != NULL)
            now->color = black;
    
    }
    void delect(Tree* tree, node* target)
    {
        node* parent, *child;
        int color;
    
        //既有左子树,又有右子树,在右子树中找后继节点
        if (target->left != NULL && target->right != NULL)
        {
            //找到后继节点(替代节点),用后继节点替换被删除的节点位置
            //然后在删除后继节点的位置
            node* replace = target;
            replace = target->right;
            while (replace->left != NULL)
                replace = replace->left;
    
            //被删除的节点不是根节点(根节点没有父亲节点)
            if (target->parent != NULL)
            {
                //这里的替换不仅仅是值得替换,还有关系上得替换
                if (target->parent->left == target)
                    target->parent->left = replace;
                else
                    target->parent->right = replace;
            }
            else
                tree->root = replace;
    
            //替换完成之后,删除target的问题就转换成删除后继节点的问题
            //现在考虑这个后继节点(replace):
            //replace节点是target右子树中最小的节点,
            //所以replace要么没有儿子节点,要么只有右儿子节点
            child = replace->right;
            parent = replace->parent;
    
            //保存后继节点的颜色
            color = replace->color;
    
            if (target == parent)
            {
                /*           target
                *             /  
                *            A  replace
                *
                *///这种情况的话先用replace替换target,然后直接删除replace,
                //只需要更新父亲关系,不用更新replace的儿子的关系(replace没有儿子)
                parent = replace;
            }
            else
            {
                /*情况一:         target          情况二       target
                *                 /                            /    
                *                A      B                      A      B
                *                      /                           /  
                *                replace   C                   replace  C
                *                                                
                *                    NULL                        child
                *
                */
                if (child != NULL)
                {
                    //情况一:直接删除replace,不用调整replace的父子关系
                    child->parent = parent;
                }
    
                //情况二:删除replace,它的后继节点就是child,接下来调整父子关系
    
                parent->left = child;
                replace->right = target->right;
                target->right->parent = replace;
            }
    
            //replace替代target之后,调整父子关系
            replace->parent = target->parent;
            replace->left = target->left;
            replace->color = target->color;
            target->left->parent = replace;
    
            //自平衡:颜色修正
            //如果replace的颜色是红色,把他替换到target位置,replace节点所在支路的黑色节点数量不变
            //如果replace的颜色是黑色,把他替换到target位置,replace节点所在支路的黑色节点数量减一
            //这样违反性质5,又需要继续调整
            if (color == black)
                delect_fixup(tree, child, parent);
            free(target);
            return;
        }
        //只有左子树或右子树,它的左儿子或右儿子就是后继节点
        //child就是replace
        if (target->left != NULL)
            child = target->left;
        else
            child = target->right;
    
        //确定后继节点之后,开始调整父子关系
        parent = target->parent;
        color = target->color;
    
        //target为叶子节点
        if (child != NULL)
            child->parent = target;
    
        //target不为根节点
        if (parent != NULL)
        {
            if (parent->left == target)
                parent->left = child;
            else
                parent->right = child;
        }
        //target为根节点
        else
            tree->root = child;
    
        //如果target的颜色是黑色的话,破坏性质5
        if (color == black)
            delect_fixup(tree, child, parent);
        free(target);
    }
    node* search(Tree* tree,int x)
    {
        node* root=tree->root;
        while(root!=NULL&&root->data!=x)
        {
            if(x<root->data)
                root=root->left;
            else
                root=root->right;
        }
        return root;
    }
    void Print(node* root)//前序遍历
    {
        if (root == NULL)
            return;
        printf("%d
    ", root->data);
        Print(root->left);
        Print(root->right);
    }
    int main()
    {
        Tree* tree = new Tree;
        tree->root = NULL;
        int n, x;
        cin >> n;
        for (int i = 0; i < n; i++)
        {
            cin >> x;
            insert(tree, Creat_Node(x, red, NULL, NULL, NULL));
        }
        Print(tree->root);
        delect(tree,search(tree,4));
        Print(tree->root);
        return 0;
    }
    完整代码

     以上部分转载自https://www.cnblogs.com/skywang12345/p/3624177.html

  • 相关阅读:
    20181020遭遇战
    二分图最大分配
    2019.11.11 洛谷月赛t3
    2019.10.29 CSP%您赛第四场t2
    2019.10.28 CSP%您赛第四场t3
    2019.10.26 CSP%您赛第三场
    2019.10.24 CSP%你赛第二场d1t3
    0080-简单的排序
    0079-简单的循环
    0078-求最大因子
  • 原文地址:https://www.cnblogs.com/-citywall123/p/12588772.html
Copyright © 2020-2023  润新知