红黑树删除

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了红黑树删除相关的知识,希望对你有一定的参考价值。

  删除操作比插入复杂一些。首先我们先来了解一些红黑树的特性。这些是我随意列举的,供大家参考。

  1、红色节点的父亲黑孩子一定是黑色。(nil是黑节点)

  2、单支节点,只能是黑红。(红,黑黑,不符合规则4,到树尾黑节点个数相同)

  3、真正的删除节点一定是单支节点或者叶子节点。(没有孩子的节点)

接下来我们讲如何找真正的删除节点。

技术分享

有左右子树的情况

如果8是删除节点,那么10就是真正的删除节点。

查找方法是,找8节点的右子树中的最小的节点,根据二叉树的性质,我们可以知道,8节点的右孩子的左孩子的左孩子……一直到left为nil的节点。就是10。(while ( y->left != nil )  y = y->left;)

单支节点或者没有叶子节点的情况

删除节点跟真正的删除节点是一个。

找到真正的删除节点后,我们把删除节点的值变成真正的删除节点的值。这时候把真正的删除节点从红黑树中剔除。

如果真正的删除节点是黑色,则破坏了红黑树的性质,进行调整。(比如删除8,真正的删除节点是10,10是黑色节点,所以需要调整红黑树)

void rb_remove(const int key, Tree * tree)
{
    Node * x, * y, * z;//x是删除点,y是真正的删除点,z是y的子节点

    x = search_node(key, tree);
    if ( x == nil || x->value != key )
        return;
    if ( x->left != nil && x->right != nil )//找到真正的删除节点
    {
        y = x->right;
        while ( y->left != nil )
            y = y->left;
    }
    else
        y = x;
    if ( y->left != nil )
        z = y->left;
    else
        z = y->right;
    
    if ( y == tree->root )
    {
        tree->root = z;
        z->parent = nil;
    }
    else
    {
        if ( y == y->parent->left )
            y->parent->left = z;
        else
            y->parent->right = z;
        if ( z != nil )
            z->parent = y->parent;
    }
    assign(x, y);

    if ( y->color == black )
        remove_fixup(z, y->parent, tree);
    free(y);
}

在将真正的删除节点剔除时,注意它是否有孩子,如果右孩子,将原来指向真正删除节点的指针指向孩子,不要忘了将更改孩子的父亲。否则将原来指向真正删除节点的指针指向nil。

接下来删除调整

删除调整的实现有很多种,不过答题思路都是一样的。如果自己写的话,需要把各种情况考虑清楚。

static void remove_fixup(Node * x, Node * y, Tree * tree)
{
    Node * z;//z是x的兄弟

    while ( x != tree->root && x->color == black )
    {
        if ( x == y->left )
        {
            z = y->right;
            //case 1:    兄弟是红色
            //处理方法:
            //        1.将兄弟设为黑色
            //        2.将父亲设为红色
            //        3.以父亲为旋转点,左旋
            //        4.重置x的兄弟节点
            //    变成case 2, 3, 4
            if ( z->color == red )
            {
                z->color = black;
                y->color = red;
                left_rotate(y, tree);
                z = y->right;
            }
            //case 2:    兄弟是黑色,并且两个孩子是黑色
            //处理方法:
            //        1.将兄弟设为红色
            //        2.将x设为父亲
            if ( z->left->color == black && z->right->color == black )
            {
                z->color = red;
                x = y;
                y = x->parent;
            }
            //case 3:    兄弟是黑色,左孩子是红色,右孩子是黑色
            //处理方法;
            //        1.将兄弟的左孩子设为黑色
            //        2.将兄弟设为红色
            //        3.以兄弟为旋转点,右旋
            //        4.重新设置兄弟节点
            else 
            {
                if ( z->right->color == black )
                {
                    z->left->color = black;
                    z->color = red;
                    right_rotate(z, tree);
                    z = y->right;
                }
                //case 4:    兄弟是黑色,右孩子是红色
                //处理方法:
                //        1.将兄弟的颜色设为父亲的颜色
                //        2.将父亲的颜色设为黑色
                //        3.将兄弟的右孩子设为黑色
                //        4.以父亲为旋转点,左旋
                z->color = y->color;
                y->color = black;
                z->right->color = black;
                left_rotate(y, tree);
                break;
            }
        }
        else
        {
            z = y->left;

            if ( z->color == red )
            {
                y->color = red;
                z->color = black;
                right_rotate(y, tree);
                z = y->left;
            }
            if ( z->left->color == black && z->right->color == black )
            {
                z->color = red;
                x = y;
                y = x->parent;
            }
            else
            {
                if ( z->left->color == black )
                {
                    z->right->color = black;
                    z->color = red;
                    left_rotate(z, tree);
                    z = y->left;
                }

                z->color = y->color;
                y->color = black;
                z->left->color = black;
                right_rotate(y, tree);
                break;
            }
        }
    }
    if ( x != nil )
        x->color = black;
}

如果x是y的右孩子,操作跟左孩子相同,把left与right交换。

删除操作完成,建议大家在调试把红黑树画出来。

这里附上我的调试代码。

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

#include "rbtree.h"

void print(Node * x)
{
    printf("%d\t", x->value);
    if ( x->color == red )
        printf("red\t");
    else
        printf("black\t");
    if ( x->parent )
        printf("parent value = %d\n", x->parent->value);
    else
        printf("\n");
}

int main(void)
{
    Tree tree;
    int i;
    
    srand(time(NULL));

    rb_init(&tree);

    for ( i = 0; i < 100; i++ )
    {
        rb_insert(rand()%1000, &tree);
    }

    rb_treaverse(&tree, print);
    for ( i = 0; i < 100; i++ )
    {
        rb_remove(rand()%1000, &tree);
    }

//    rb_insert(10, &tree);
//    rb_insert(7, &tree);
//    rb_insert(8, &tree);
//    rb_insert(15, &tree);
//    rb_insert(5, &tree);
//    rb_insert(6, &tree);
//    rb_insert(11, &tree);
//    rb_insert(13, &tree);
//    rb_insert(12, &tree);
//    rb_insert(2, &tree);
//
//    rb_treaverse(&tree, print);
//    rb_remove(5, &tree);
//    rb_remove(7, &tree);
//    rb_remove(6, &tree);
//    rb_remove(8, &tree);
//    rb_treaverse(&tree, print);
//    rb_remove(2, &tree);
//    rb_remove(10, &tree);
//    rb_remove(11, &tree);
//    rb_remove(12, &tree);
//    rb_remove(15, &tree);
//    rb_remove(13, &tree);

    rb_treaverse(&tree, print);

    return 0;
}

前边的两个for循环主要测试红黑树是否有bug,如果发现bug,用下面的插入查找bug。

下边的代码四种情况基本都会用到。

 

以上是关于红黑树删除的主要内容,如果未能解决你的问题,请参考以下文章

红黑树的旋转查找和删除(附源代码)

使用红黑树的字典 - 删除错误

算法导论 红黑树 学习 删除

C++-红黑树的插入和删除实现

红黑树之删除节点

基于234树手撕TreeMap红黑树源码(3万字长文带你走进红黑深处的细节)