紅黑樹(三)刪除

刪除操作比插入複雜一些。首先我們先來了解一些紅黑樹的特性。這些是我隨意列舉的,供大家參考。

  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。

下邊的代碼四種情況基本都會用到。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章