红黑树插入删除节点过程分析 && C代码实现

红黑树的插入和删除规则:

红黑树的五个性质

1、    每个节点要么是红的,要么是黑的

2、    根节点时黑色的

3、    每个叶节点(叶节点既指树尾端NIL指针或NULL节点)是黑色的

4、    如果一个节点时红的,那么它的两个儿子都是黑色的

5、    对每个节点,其到叶节点树尾端NIL指针的每一条路径都包含相同数目的黑节点

这里所说的“叶节点”或者“NULL节点”,它不包含数据而只充当树在此结束的知识。

二叉树的左旋和右旋这里不再讲解

红黑树的插入操作:

在对红黑树进行插入操作时,我们一般总是插入红色的节点,因为这样可以在插入过程中尽量避免对树的调整。

如果插入的节点是根节点,性质2会被破坏,如果插入节点的父是红色,则会破坏性质4.因此,总而言之,插入一个红色节点只会破坏性质2或性质4

其实可以简单处理:

把出现违背红黑树性质的节点向上移动,如果能移动到根节点,那么很容易就能通过直接修改根节点来恢复红黑树的性质,直接通过修改根节点来恢复红黑树应满足的性质。

其二,穷举所有的可能性,之后把能归于同一类方法处理的归为同一类,不能直接处理的归化到下面的几种情况。

插入修复的操作:

情况1:插入的是根节点

直接把此节点涂为黑色

情况2:插入的节点的父节点的黑色

此不会违反性质2和性质4,红黑树没有被破坏 什么都不做

情况3:当前节点的父节点时红色且祖父节点的另一个子节点(叔叔节点)是红色

此时父节点的父节点一定存在,否则插入前就已不是红黑树。与此同时,又分为父节点是祖父节点的左子还是右子,对于对称性,只要知道一个方向即可。

在这里,我们只考虑父节点为祖父左子的情况。

同时,还可以分为当前节点是其父节点的左子还是右子,但是处理方式是一样的。

对策:将当前节点的父节点和叔叔节点涂黑,祖父节点涂红,把当前节点指向祖父节点,从新的当前节点重新开始算法

情况4:当前节点的父节点是红色,叔叔节点是黑色的,当前节点是其父节点的右子

对策:当前节点的父节点作为新的当前节点,以新当前节点为支点左旋

情况5:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的左子

对策:父节点变为黑色,祖父节点变为红色,在祖父节点为支点右旋

红黑树的删除操作:

我们删除的节点的方法与常规二叉搜索树中删除节点的方法是一样的,如果被删除的节点不是有双非空子女,则直接删除这个节点,用它的位移子节点顶替他的位置,如果他的子节点分是空节点,那就用空节点顶替他的位置,如果他的双子全为非空,我们就把它的直接后继节点内容复制到它的位置,之后以同样的方法删除他的后继节点,他的后继节点不可能是双子非空,因此此传递最多只进行一次

在这里说明一下二叉树节点删除的几种情况,待删除的节点按照儿子的个数可以分为三种:

1、    没有儿子,既为叶子节点。直接把父节点的对应儿子指针设为NULL,删除儿子节点就OK

2、    只有一个儿子。那么把父节点的相应儿子指针指向儿子的独生子,删除儿子节点也OK

3、    有两个儿子,这是组麻烦的情况,因为你删除节点之后,还要保证满足搜索二叉树的结构。其实也比较简单,我们可以选择左儿子中的最大元素或者右儿子的最小元素放到待删除节点的位置,就可以保证结构的不变。当然,你要记得调整子树,毕竟又出现了节点删除。习惯上选择左儿子中的最大元素,其实选择右儿子的最小元素也一样,没有任何差别。只是习惯而已。这里咱们也选择左儿子的最大元素,将它放到待删除节点的位置。左儿子的最大元素其实很好找,只要顺着左儿子不断的去搜索右字数就可以了,直到找到一个没有右子树的节点,那就是最大的了

在删除节点后,原红黑树的性质可能被改变,如果删除的是红色节点,那么原红黑树的性质依旧保持,此时不用做修正操作,如果删除的节点是黑色节点,原红黑树的性质可能会被改变,我们要对其做修正操作。那些性质可能被修改呢?如果删除节点不是树唯一节点,那么删除节点的那一个支的到各叶节点的黑色节点数会发生改变,此时性质5被破坏。如果被删除节点的唯一非空子节点是红色,而被删除节点的父节点也是红色,那么性质4被破坏。如果被删除节点是根节点,而它的唯一非空子节点是红色,则删除后新根节点将变成红色,违背性质2.

情况1:当前节点是红色

情况2:当前节点是黑色且是根节点

情况3:当前节点是黑且兄弟节点为红色(此时父节点和兄弟节点的子节点为黑)

情况4:当前节点是黑且兄弟节点为黑且兄弟节点的两个子节点全为黑色

情况5:当前节点是黑兄弟节点是黑,兄弟的左子是红色,右子是黑色

情况6:当前节点是黑兄弟节点是黑色,但是兄弟节点的右子是红色,兄弟节点左子的颜色任意

其实对于红黑树的插入操作来说,需要明白为什么在插入节点时需要关注叔叔节点。对于插入操作来说,插入的是红色节点,如果父亲节点是黑色节点,那么不需要过多操作,需要关注的父亲节点是红色的情况,那么就需要看叔叔节点什么颜色了,如果叔叔是红色,那么祖父肯定是黑色,如果叔叔是黑色,祖父存在也是红色。

对于删除操作,就需要关注兄弟节点,对于兄弟节点需要关注的就是兄弟的儿子节点了。

代码实现:

    //一、左旋代码分析
    /*-----------------------------------------------------------
    |   node           right
    |   / \    ==>     / \
    |   a  right     node  y
    |       / \       / \
    |       b  y     a   b    //左旋
    -----------------------------------------------------------*/
    static rb_node_t* rb_rotate_left(rb_node_t* node, rb_node_t* root)
    {
        rb_node_t* right = node->right;    //指定指针指向 right<--node->right  

        if ((node->right = right->left))
        {
            right->left->parent = node;  //好比上面的注释图,node成为b的父母
        }
        right->left = node;   //node成为right的左孩子  

        if ((right->parent = node->parent))
        {
            if (node == node->parent->right)
            {
                node->parent->right = right;
            }
            else
            {
                node->parent->left = right;
            }
        }
        else
        {
            root = right;
        }
        node->parent = right;  //right成为node的父母  

        return root;
    }  

    //二、右旋
    /*-----------------------------------------------------------
    |       node            left
    |       / \             / \
    |    left  y   ==>    a    node
    |   / \                    / \
    |  a   b                  b   y  //右旋与左旋差不多,分析略过
    -----------------------------------------------------------*/
    static rb_node_t* rb_rotate_right(rb_node_t* node, rb_node_t* root)
    {
        rb_node_t* left = node->left;  

        if ((node->left = left->right))
        {
            left->right->parent = node;
        }
        left->right = node;  

        if ((left->parent = node->parent))
        {
            if (node == node->parent->right)
            {
                node->parent->right = left;
            }
            else
            {
                node->parent->left = left;
            }
        }
        else
        {
            root = left;
        }
        node->parent = left;  

        return root;
    }  

    //三、红黑树查找结点
    //----------------------------------------------------
    //rb_search_auxiliary:查找
    //rb_node_t* rb_search:返回找到的结点
    //----------------------------------------------------
    static rb_node_t* rb_search_auxiliary(key_t key, rb_node_t* root, rb_node_t** save)
    {
        rb_node_t *node = root, *parent = NULL;
        int ret;  

        while (node)
        {
            parent = node;
            ret = node->key - key;
            if (0 < ret)
            {
                node = node->left;
            }
            else if (0 > ret)
            {
                node = node->right;
            }
            else
            {
                return node;
            }
        }  

        if (save)
        {
            *save = parent;
        }  

        return NULL;
    }  

    //返回上述rb_search_auxiliary查找结果
    rb_node_t* rb_search(key_t key, rb_node_t* root)
    {
        return rb_search_auxiliary(key, root, NULL);
    }  

    //四、红黑树的插入
    //---------------------------------------------------------
    //红黑树的插入结点
    rb_node_t* rb_insert(key_t key, data_t data, rb_node_t* root)
    {
        rb_node_t *parent = NULL, *node;  

        parent = NULL;
        if ((node = rb_search_auxiliary(key, root, &parent)))  //调用rb_search_auxiliary找到插入结  

    点的地方
        {
            return root;
        }  

        node = rb_new_node(key, data);  //分配结点
        node->parent = parent;
        node->left = node->right = NULL;
        node->color = RED;  

        if (parent)
        {
            if (parent->key > key)
            {
                parent->left = node;
            }
            else
            {
                parent->right = node;
            }
        }
        else
        {
            root = node;
        }  

        return rb_insert_rebalance(node, root);   //插入结点后,调用rb_insert_rebalance修复红黑树  

    的性质
    }  

    //五、红黑树的3种插入情况
    //接下来,咱们重点分析针对红黑树插入的3种情况,而进行的修复工作。
    //--------------------------------------------------------------
    //红黑树修复插入的3种情况
    //为了在下面的注释中表示方便,也为了让下述代码与我的倆篇文章相对应,
    //用z表示当前结点,p[z]表示父母、p[p[z]]表示祖父、y表示叔叔。
    //--------------------------------------------------------------
    static rb_node_t* rb_insert_rebalance(rb_node_t *node, rb_node_t *root)
    {
        rb_node_t *parent, *gparent, *uncle, *tmp;  //父母p[z]、祖父p[p[z]]、叔叔y、临时结点*tmp  

        while ((parent = node->parent) && parent->color == RED)
        {     //parent 为node的父母,且当父母的颜色为红时
            gparent = parent->parent;   //gparent为祖父  

            if (parent == gparent->left)  //当祖父的左孩子即为父母时。
                                     //其实上述几行语句,无非就是理顺孩子、父母、祖父的关系。:D。
            {
                uncle = gparent->right;  //定义叔叔的概念,叔叔y就是父母的右孩子。  

                if (uncle && uncle->color == RED) //情况1:z的叔叔y是红色的
                {
                    uncle->color = BLACK;   //将叔叔结点y着为黑色
                    parent->color = BLACK;  //z的父母p[z]也着为黑色。解决z,p[z]都是红色的问题。
                    gparent->color = RED;
                    node = gparent;     //将祖父当做新增结点z,指针z上移俩层,且着为红色。
        //上述情况1中,只考虑了z作为父母的右孩子的情况。
                }
                else                     //情况2:z的叔叔y是黑色的,
                {
                    if (parent->right == node)  //且z为右孩子
                    {
                        root = rb_rotate_left(parent, root); //左旋[结点z,与父母结点]
                        tmp = parent;
                        parent = node;
                        node = tmp;     //parent与node 互换角色
                    }
                                 //情况3:z的叔叔y是黑色的,此时z成为了左孩子。
                                        //注意,1:情况3是由上述情况2变化而来的。
                                        //......2:z的叔叔总是黑色的,否则就是情况1了。
                    parent->color = BLACK;   //z的父母p[z]着为黑色
                    gparent->color = RED;    //原祖父结点着为红色
                    root = rb_rotate_right(gparent, root); //右旋[结点z,与祖父结点]
                }
            }   

            else
            {
       //这部分是特别为情况1中,z作为左孩子情况,而写的。
                uncle = gparent->left;  //祖父的左孩子作为叔叔结点。[原理还是与上部分一样的]
                if (uncle && uncle->color == RED)  //情况1:z的叔叔y是红色的
                {
                    uncle->color = BLACK;
                    parent->color = BLACK;
                    gparent->color = RED;
                    node = gparent;           //同上。
                }
                else                               //情况2:z的叔叔y是黑色的,
                {
                    if (parent->left == node)  //且z为左孩子
                    {
                        root = rb_rotate_right(parent, root);  //以结点parent、root右旋
                        tmp = parent;
                        parent = node;
                        node = tmp;       //parent与node 互换角色
                    }
                      //经过情况2的变化,成为了情况3.
                    parent->color = BLACK;
                    gparent->color = RED;
                    root = rb_rotate_left(gparent, root);   //以结点gparent和root左旋
                }
            }
        }  

        root->color = BLACK; //根结点,不论怎样,都得置为黑色。
        return root;      //返回根结点。
    }  

    //六、红黑树的删除
    //------------------------------------------------------------
    //红黑树的删除结点
    rb_node_t* rb_erase(key_t key, rb_node_t *root)
    {
        rb_node_t *child, *parent, *old, *left, *node;
        color_t color;  

        if (!(node = rb_search_auxiliary(key, root, NULL)))  //调用rb_search_auxiliary查找要删除的  

    结点
        {
            printf("key %d is not exist!\n");
            return root;
        }  

        old = node;  

        if (node->left && node->right)
        {
            node = node->right;
            while ((left = node->left) != NULL)
            {
                node = left;
            }
            child = node->right;
            parent = node->parent;
            color = node->color;  

            if (child)
            {
                child->parent = parent;
            }
            if (parent)
            {
                if (parent->left == node)
                {
                    parent->left = child;
                }
                else
                {
                    parent->right = child;
                }
            }
            else
            {
                root = child;
            }  

            if (node->parent == old)
            {
                parent = node;
            }  

            node->parent = old->parent;
            node->color = old->color;
            node->right = old->right;
            node->left = old->left;  

            if (old->parent)
            {
                if (old->parent->left == old)
                {
                    old->parent->left = node;
                }
                else
                {
                    old->parent->right = node;
                }
            }
            else
            {
                root = node;
            }  

            old->left->parent = node;
            if (old->right)
            {
                old->right->parent = node;
            }
        }
        else
        {
            if (!node->left)
            {
                child = node->right;
            }
            else if (!node->right)
            {
                child = node->left;
            }
            parent = node->parent;
            color = node->color;  

            if (child)
            {
                child->parent = parent;
            }
            if (parent)
            {
                if (parent->left == node)
                {
                    parent->left = child;
                }
                else
                {
                    parent->right = child;
                }
            }
            else
            {
                root = child;
            }
        }  

        free(old);  

        if (color == BLACK)
        {
            root = rb_erase_rebalance(child, parent, root); //调用rb_erase_rebalance来恢复红黑树性  

    质
        }  

        return root;
    }  

    //七、红黑树的4种删除情况
    //----------------------------------------------------------------
    //红黑树修复删除的4种情况
    //为了表示下述注释的方便,也为了让下述代码与我的倆篇文章相对应,
    //x表示要删除的结点,*other、w表示兄弟结点,
    //----------------------------------------------------------------
    static rb_node_t* rb_erase_rebalance(rb_node_t *node, rb_node_t *parent, rb_node_t *root)
    {
        rb_node_t *other, *o_left, *o_right;   //x的兄弟*other,兄弟左孩子*o_left,*o_right  

        while ((!node || node->color == BLACK) && node != root)
        {
            if (parent->left == node)
            {
                other = parent->right;
                if (other->color == RED)   //情况1:x的兄弟w是红色的
                {
                    other->color = BLACK;
                    parent->color = RED;   //上俩行,改变颜色,w->黑、p[x]->红。
                    root = rb_rotate_left(parent, root);  //再对p[x]做一次左旋
                    other = parent->right;  //x的新兄弟new w 是旋转之前w的某个孩子。其实就是左旋后  

    的效果。
                }
                if ((!other->left || other->left->color == BLACK) &&
                    (!other->right || other->right->color == BLACK))
                              //情况2:x的兄弟w是黑色,且w的俩个孩子也  

    都是黑色的  

                {                         //由于w和w的俩个孩子都是黑色的,则在x和w上得去掉一黑色,
                    other->color = RED;   //于是,兄弟w变为红色。
                    node = parent;    //p[x]为新结点x
                    parent = node->parent;  //x<-p[x]
                }
                else                       //情况3:x的兄弟w是黑色的,
                {                          //且,w的左孩子是红色,右孩子为黑色。
                    if (!other->right || other->right->color == BLACK)
                    {
                        if ((o_left = other->left))   //w和其左孩子left[w],颜色交换。
                        {
                            o_left->color = BLACK;    //w的左孩子变为由黑->红色
                        }
                        other->color = RED;           //w由黑->红
                        root = rb_rotate_right(other, root);  //再对w进行右旋,从而红黑性质恢复。
                        other = parent->right;        //变化后的,父结点的右孩子,作为新的兄弟结点  

    w。
                    }
                                //情况4:x的兄弟w是黑色的  

                    other->color = parent->color;  //把兄弟节点染成当前节点父节点的颜色。
                    parent->color = BLACK;  //把当前节点父节点染成黑色
                    if (other->right)      //且w的右孩子是红
                    {
                        other->right->color = BLACK;  //兄弟节点w右孩子染成黑色
                    }
                    root = rb_rotate_left(parent, root);  //并再做一次左旋
                    node = root;   //并把x置为根。
                    break;
                }
            }
      //下述情况与上述情况,原理一致。分析略。
            else
            {
                other = parent->left;
                if (other->color == RED)
                {
                    other->color = BLACK;
                    parent->color = RED;
                    root = rb_rotate_right(parent, root);
                    other = parent->left;
                }
                if ((!other->left || other->left->color == BLACK) &&
                    (!other->right || other->right->color == BLACK))
                {
                    other->color = RED;
                    node = parent;
                    parent = node->parent;
                }
                else
                {
                    if (!other->left || other->left->color == BLACK)
                    {
                        if ((o_right = other->right))
                        {
                            o_right->color = BLACK;
                        }
                        other->color = RED;
                        root = rb_rotate_left(other, root);
                        other = parent->left;
                    }
                    other->color = parent->color;
                    parent->color = BLACK;
                    if (other->left)
                    {
                        other->left->color = BLACK;
                    }
                    root = rb_rotate_right(parent, root);
                    node = root;
                    break;
                }
            }
        }  

        if (node)
        {
            node->color = BLACK;  //最后将node[上述步骤置为了根结点],改为黑色。
        }
        return root;  //返回root
    }  

    //八、测试用例
    //主函数
    int main()
    {
        int i, count = 100;
        key_t key;
        rb_node_t* root = NULL, *node = NULL;  

        srand(time(NULL));
        for (i = 1; i < count; ++i)
        {
            key = rand() % count;
            if ((root = rb_insert(key, i, root)))
            {
                printf("[i = %d] insert key %d success!\n", i, key);
            }
            else
            {
                printf("[i = %d] insert key %d error!\n", i, key);
                exit(-1);
            }  

            if ((node = rb_search(key, root)))
            {
                printf("[i = %d] search key %d success!\n", i, key);
            }
            else
            {
                printf("[i = %d] search key %d error!\n", i, key);
                exit(-1);
            }
            if (!(i % 10))
            {
                if ((root = rb_erase(key, root)))
                {
                    printf("[i = %d] erase key %d success\n", i, key);
                }
                else
                {
                    printf("[i = %d] erase key %d error\n", i, key);
                }
            }
        }  

        return 0;
    }  

参考:http://blog.csdn.net/v_july_v/article/details/6114226  代码来自结构之法算法之道

时间: 2024-10-13 10:35:58

红黑树插入删除节点过程分析 && C代码实现的相关文章

数据结构——红黑树的删除节点操作

1. 红黑树的删除操作过程分析 1. 首先看一下普通搜索二叉树的删除操作,普通搜索二叉树删除结点找替代结点有3种情情景: 情景1:删除结点无子结点,直接删除 情景2:删除结点只有一个子结点 情景3:删除结点有两个子结点 在这里有一个重要的思路:删除结点被替代后,在不考虑结点的键值的情况下,对于树来说,可以认为删除的是替代结点. 基于此,上面所说的3种二叉树的删除情景可以相互转换并且最终都是转换为情景1. 对于情景2:删除结点用其唯一的子结点替换,子结点替换为删除结点后,直接删除 对于情景3:删除

红黑树之删除节点

红黑树之删除节点 上一篇文章中讲了如何向红黑树中添加节点,也顺便创建了一棵红黑树.今天写写怎样从红黑树中删除节点. 相比于添加节点,删除节点要复杂的多.不过我们慢慢梳理,还是能够弄明白的. 回顾一下红黑树的性质 红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色.在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求: 节点是红色或黑色. 根节点是黑色. 每个叶节点(这里的叶节点是指NULL节点,在<算法导论>中这个节点叫哨兵节点,除了颜色属性外,其他属性值都为任

红黑树(3) - 删除操作

在本系列的前面两篇文章中,已经介绍了红黑树以及其插入操作.具体可参考下面两个链接: 红黑树(1) - 介绍 红黑树(2) - 插入操作 1.删除操作介绍 类似于插入操作,红黑树进行删除节点时,也使用重新着色以及旋转这两种方式,来维护它的属性.在插入操作中,我们主要是依靠检测叔节点的颜色来决定哪种场景.在删除操作中,我们使用检测兄弟的颜色,来决定是哪种场景. 在插入操作中,最常见的违反红黑树属性的一种情况是存在两个连续的红色节点.而在删除操作中,常见的情况是,当删除节点是黑色时,会影响从根节点到叶

红黑树插入与删除完整代码(dart语言实现)

之前分析了红黑树的删除,这里附上红黑树的完整版代码,包括查找.插入.删除等.删除后修复实现了两种算法,均比之前的更为简洁.一种是我自己的实现,代码非常简洁,行数更少:一种是Linux.Java等源码版本的实现,实现的略为复杂,但效率更高.两种算法经过测试,在百万级的数据上效率不分伯仲:1000万的数据中,我自己的实现比Linux内核版本的运行时间多2秒左右. 红黑树的插入相对简单,本文中的代码实现与Linux源码版本也略有差异,效率差别不大. 其他方法,如查找.遍历等,比较简单,不多做解释.遍历

红黑树 插入

红黑树是一种二叉搜索树,每个节点增加一位来储存节点的颜色,红或黑. 红黑树通过如何一条从跟到叶子的路径上各个节点的着色方式的限制,红黑树确保没有一条路径比另一条路径长两倍. 其他性质: 根节点是黑色. 红色节点的子节点都为黑色. 叶子节点或NULL空节点都为黑色.所有原来的叶子节点都有NULL的空节点作为新的叶子节点,取代了自己的叶子节点的身份,即,所有的NULL节点都是黑色的意思. 最重要一点:任何一节点到叶子节点的NULL指针的路径经过的黑色节点数相同. 插入: 左旋 以节点p为支点的话,即

红黑树之添加节点和创建

红黑树之插入节点 红黑树的性质 红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色.在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求: 节点是红色或黑色. 根节点是黑色. 每个叶节点(这里的叶节点是指NULL节点,在<算法导论>中这个节点叫哨兵节点,除了颜色属性外,其他属性值都为任意.为了和以前的叶子节点做区分,原来的叶子节点还叫叶子节点,这个节点就叫他NULL节点吧)是黑色的. 每个红色节点的两个子节点都是黑色.(从每个叶子到根的所有路径上不能有两个连续的

算法导论 之 红黑树 - 插入[C语言]

作者:邹祁峰 邮箱:[email protected] 博客:http://blog.csdn.net/qifengzou 日期:2013.12.24 21:00 转载请注明来自"祁峰"的CSDN博客 1 引言 在之前的博文中,本人对平衡二叉树的处理做了较详尽的分析,有兴趣的朋友可以参阅博文<算法导论 之 平衡二叉树 - 创建 插入 搜索 销毁>和<算法导论 之 平衡二叉树 - 删除>.平衡二叉树AVL是严格的平衡树,在增删结点时,其旋转操作的次数较多:而红黑树

红黑树的删除操作详解

注:本文转载自博客园,博主原址:http://www.cnblogs.com/tongy0/p/5460623.html,感谢博主帮我弄清楚了红黑树删除操作,转载做收藏用. 红黑树的删除操作 1:节点命名约定 D表示要被删除的节点.即:取 Delete 的首字母: P 表示父节点.即:取 Parent 的首字母: S表示兄弟姐妹节点.即:取 Sibling的首字母: U表示叔伯节点.即:取Uncle的首字母: G表示祖父节点.即:取 Grandfather的首字母: L表示左树.即:取Left的

红黑树-插入操作

红黑树的五个性质: 1)每个结点要么是红的,要么是黑的. 2)根结点是黑的. 3)每个叶结点,即空结点(NIL)是黑的. 4)如果一个结点是红的,那么它的俩个儿子都是黑的. 5)对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点. 红黑树插入的几种情况: 1.树是空的,直接将节点设置为根节点,颜色为黑: public void case1(RBnode T,RBnode newNode){        if(newNode.getParent()==null){