算法导论之红黑树

本文深入讲解红黑树的基本概念、性质、插入与删除操作及其实现细节。红黑树是一种自平衡二叉搜索树,通过对节点颜色的约束保证树的近似平衡。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

         红黑树是一颗二叉搜索树,它在每个节点增加了一个存储位来表示节点的颜色,可以是RED或BLACK。通过对任何一条从根到叶子的简单路径上各个节点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因而是近似平衡的。

       树中每个节点包含5个属性:color、key、left、right和p。如果一个节点没有子节点或父节点,则该节点相应指针属性的值为NULL。我们可以把这些NULL视为指向二叉搜索树的叶节点(外部节点)的指针,而把带关键字的节点视为树的内部节点。

      一颗红黑树是满足下面红黑性质的二叉搜索树:

      1.每个节点或是红色的,或是黑色的。

      2.根结点是黑色的。

      3.每个叶节点(NULL)是黑色的。

      4.如果一个节点是红色的,则它的两个子节点都是黑色的。

      5.对每个节点,从该节点到所有后代叶节点的简单路径上,均包含数目相同的黑色节点

   例:给大家举出一个红黑数的例子:(其中节点的颜色用r或b进行区分)

                                                                                               26

                                                                                 /                             \

                                                                          17                             41

                                                                      /             \                    /              \

                                                                 14                  21                30             47

                                                             /             \           /      \           /          \         /        \

                                                           10       16       19     23      28       38  NU     NU

                                                                              /    \      /     \        /   \     /    \     /  \       /   \

                                                      7     12  15  NU  NU 20NUNUNUNU  3539

                                                                    /      \    /   \   /   \              /  \                       /  \   /   \

                                                  3 NUNUNU NU NU      NU NU                NU NU NU NU

                                                  /  \

                                                NU NU

为了便于处理红黑树中代码的边界条件,使用一个哨兵来表示NULL。对于一颗红黑树T,哨兵T.nil是一个与树中普通节点有相同属性的对象。它的color属性为黑色,而其他属性p、left、right和key可以设为任意值。所有指向NULL的指针都可以用指向哨兵T.nil的指针替换。

       使用哨兵后,就可以将节点x的NULL孩子视为一个普通节点,其父节点为x。尽管可以为树内的每一个NULL新增一个不同的哨兵结束,使得每一个NULL的父节点都有这样的良定义,但这种做法会浪费空间。取而代之的是,使用一个哨兵T.nil来代替所有的NULL:所有叶节点和根节点的父节点。

     下面我们给大家介绍黑高的概念:
      黑高(black-height),记为bh(x),从某个节点x出发(不含该节点)到达一个叶节点的任意一条简单路径上的黑色节点个数称为该节点的黑高,黑高的概念是明确定义的,因为从该节点出发的所有下降到其叶节点的简单路径的黑节点个数相同。于是定义红黑树的黑高为其根结点的黑高。

     (一、)旋转

           指针结构的修改是通过旋转(ratation)来完成的,这是一种能保持二叉搜索树性质的搜索树的局部操作。下面给出两种旋转的图示:

                              Y                 LEFT _ROTATE(T,x)                       X

                           /     \                <----------------------------                   /   \

                         X      c              RIGHT_ROTATE(T,x)                  a      Y

                       /   \                      ------------------------------>                     /   \

                     a    b                                                                               b     c


            下面给出一个左旋Left_rotate操作修改二叉搜索树的例子。Right_rotate操作的代码是对称的。Left_rotate和Right_rotate都在o(1)时间内完成。在旋转操作中只有指针改变,其他所有属性都保持不变。

                                                                                 7

                                                                     /                         \

                                                             4                                     11(X)

                                                      /             \                      /                     \

                                                  3                   6              9                         18(Y)

                                            /                                                                 /          \

                                          2                                                              14          19

                                                                                                       /       \             \

                                                                                                    12     17           22

                                                                                                                              /

                                                                                                                           20

          进行左旋后的二叉搜索树:
                                                                                  7

                                                                     /                         \

                                                             4                                     18

                                                      /             \                      /                     \

                                                  3                   6             11                        19

                                            /                                       /      \                            \

                                          2                                      9       14                        22

                                                                                        /       \                       /

                                                                                      12     17                 20

下面给出左旋和右旋的代码:

static Rbtree Left_rotate(Rbtree root,Rbtree_node *node)   //左旋
{
    Rbtree_node *node_right = NULL;
    //node_right为node的右孩子
    node_right = node ->right_child;
    //将node_right的左孩子赋给node的右孩子
    node ->right_child = node_right ->left_child;
    if(node_right ->left_child){
        //修改node_right左孩子的父节点
        node_right ->left_child ->parent = node;
    }
    //修改node_right的父节点
    node_right ->parent = node ->parent;
    if(node ->parent == NULL){
        root = node_right;
    }else if(node ->parent ->left_child == node){
        node ->parent ->left_child = node_right;
    }else{
        node ->parent ->right_child = node_right;
    }
    node_right ->left_child = node;
    node ->parent = node_right;
   
   return root; 
}
static Rbtree Right_rotate(Rbtree root,Rbtree_node *node)  //右旋
{
    Rbtree_node *node_left = NULL;
   
    node_left = node ->left_child;
    node ->left_child = node_left ->right_child;
    if(node_left ->right_child){
        node_left ->right_child ->parent = node;
    }
    node_left ->parent = node ->parent;
    if(node ->parent == NULL){
        root = node_left;
    }else if(node ->parent ->right_child == node){
        node ->parent ->right_child = node_left;
    }else{
        node ->parent ->left_child = node_left;
    }
    node_left ->right_child = node;
    node ->parent = node_left;
   
   return root; 

}

       (   二、) 红黑树的插入操作:
                   我们可以在o(log n)的时间复杂度内完成一颗含n个节点的红黑树中插入一个新节点,我们可以利用之前在二叉搜索树中的Tree_insert的过程,在之前的基础上稍作修改就可以将新节点插入到红黑树中,就好像T是一颗普通的二叉搜索树一样,然后将其着为红色。为保证红黑性质能继续保持。我们需要节点进行着色和重新旋转。

               首先我们来分析,当插入一个新的节点时,红黑树的哪些性质会被破坏?性质1到性质3继续成立,因为新插入的红节点的两个子节点都是哨兵T.nil,性质5,即一个指定节点开始的每条简单路径上的黑节点的个数都是相等的,也会成立,因为新节点代替了(黑色)哨兵,并且新节点是本身是有哨兵孩子的红节点。这样看来,仅可能破坏的就是性质2和性质4,即根结点需要为黑色以及一个红节点不能有红孩子。这两个性质可能被破坏是因为新节点被着为红色,如果新节点是根结点,则破坏性质2 ,如果新节点的父节点是红节点,则破坏了性质4。

               下面我们来讨论如何进行调整:
        情况一:

             首先给出红黑树:

                                                                   11

                                                                /             \

                                                             2             14

                                                           /     \                  \

                                                         1     7 (gparent)  15

                                                               /    \

                                             (parent) 5      8 (uncle)  

                                                            /                                                                                                                

                                                          4 (node)                                                


                   parent = gparent ->left,并且uncle = red;

                   此时执行的操作是:
                   parent = black;

                   uncle = black;

                   gparent = red;

                    node = gparent;

           情况二: 此时执行操作后红黑树的状态如下图:    

                                                                     11(gparent)

                                                                /             \

                                                (parent)2             14 (uncle)

                                                           /     \                  \

                                                         1     7 (node)15

                                                               /    \

                                                            5      8  

                                                            /                                                                                                                

                                                          4  

                                                                                           

                           parent = gparent ->left,并且node = parent ->right;

                           此时执行操作:

                           左旋(parent,root);

                          swap(&parent,&node,sizeof(parent))

           情况三:

                                                                        11(gparent)

                                                                /             \

                                                (parent)7             14 (uncle)

                                                           /     \                  \

                                             (node)2      8               15

                                                       /    \

                                                      1    5

                                                             /

                                                            4

                            执行操作:

                              parent = black;

                              gparent = red;

                               右旋(gparent,root);

                           得到的红黑树如图所示:
                                                                       7

                                                                /             \

                                                               2              11

                                                           /     \           /     \

                                                         1       5      8      14

                                                                  /                    \

                                                                4                    15

                                                       

             基本就可以划分为这三种情况,对于parent = gparent ->right 也是同样的,在这里不再赘述;其插入的代码如下:

static Rbtree_node *rb_search_auxilary(int data,
                                       Rbtree_node *root,Rbtree_node **save)
{
    Rbtree_node *parent = NULL;
    Rbtree_node *node = NULL;
    node = root;
    
    while(node != NULL){
        parent = node;
        if(node ->data > data){
            node = node ->left_child;
        }else if(node ->data < data){
            node = node ->right_child;
        }else{
            return node;
        }
    }
    if(save != NULL){
        *save = parent;
    }
    return NULL;
}
Rbtree_node *rb_insert(int data,Rbtree root)   //红黑树的插入操作
{
    Rbtree_node *parent = NULL;
    Rbtree_node *node = NULL;
    if((node = rb_search_auxilary(data,root,&parent)) != NULL){
        return root;
    }

    node = creat_tree_node();
    node ->data = data;
    node ->parent = parent;
    node ->left_child = NULL;
    node ->right_child = NULL;
    node ->color = RED;
   
    if(parent != NULL){
        if(parent ->data > data){
            parent ->left_child = node;
        }else{
            parent ->right_child = node;
        }
    }else{
        root = node;
    }    
    return rb_insert_rebalance(node,root);
}
static void swap(void *a,void *b,int length)
{
    void *temp = Malloc(length);
    memcpy(temp,a,sizeof(length));
    memcpy(a,b,sizeof(length));
    memcpy(b,temp,sizeof(length));
    free(temp);
}
static Rbtree_node *rb_insert_rebalance(Rbtree_node *node,Rbtree root)
{
    Rbtree_node *parent = NULL;    //父母节点
    Rbtree_node *gparent = NULL;    //祖父节点
    Rbtree_node *uncle = NULL;    //叔叔节点
    
    while((parent = node ->parent) && parent ->color == RED){
        gparent = parent ->parent;    //node祖父节点
        if(parent == gparent ->left_child){    
            uncle = gparent ->right_child;
            //node双亲节点是左孩子,node叔叔节点是右孩子
            //情况一:node叔叔节点为红色
            if(uncle != NULL && uncle == RED){
                parent ->color = BLACK;
                uncle ->color = BLACK;
                gparent ->color = RED;
                node = gparent;
            }else{
                //情况二:node叔叔节点是黑色
                if(node == parent ->right_child){
                    root = Left_rotate(parent,root);    //左旋
                    swap(&parent,&node,sizeof(parent));
                }
                //情况三:node的叔叔节点是黑色,并且node为parent的左孩子
                parent ->color = BLACK;
                gparent ->color = RED;
                root = Right_rotate(gparent,root);
            }
        
        }else{
            //node的双亲节点为右孩子,node的叔叔节点为左孩子
            uncle = gparent ->left_child;
            if(uncle != NULL && uncle ->color == RED){
                //情况一:叔叔节点为红色
                uncle ->color = BLACK;
                parent ->color = BLACK;
                gparent ->color = RED;
                node = gparent;
            }else{
                //情况二:叔叔节点为黑色,并且node为左孩子
                if(node == parent ->left_child){
                    root = Right_rotate(parent,root);
                    swap(&parent,&node,sizeof(parent));
                }
                //情况三:叔叔节点为黑色,并且node为右孩子
                parent ->color = BLACK;
                gparent ->color = RED;
                root = Left_rotate(gparent,root);
            }
        }
    }
    root ->color = BLACK;
    return root;
}

     (三、)红黑树的删除操作:

                从一颗红黑树上删除节点的过程是基于二叉搜索树的Tree_delete过程而来的,RB_delete与Tree_delete类似,只是多了几行伪代码记录节点的颜色,下面我们来看具体的删除和调整过程:

               大致的情况可以分为4种:

               情况一:node的兄弟节点other为红色:

                                              B(parent)                                                                                        D

                                           /      \                                    other = black                                       /     \

                              (node)A        D(other)                   ----------------->                                    B      E

                                       /    \      /   \                             parent = red                                      /   \     /   \

                                                  C   E                           右旋(parent,root)                   A    C

                                                 /  \   /  \                                                                                  /  \   /  \

                情况二:node的兄弟节点other为黑色,并且其左右子女都为黑色:

                                              B(parent)                                                                                       B

                                           /      \                                    other = RED                                       /     \

                              (node)A        D(other)                   ----------------->                                     A     D

                                       /    \      /   \                                                                                        /   \     /   \

                                                  C   E                                                                                             C    E

                                                 /  \   /  \                                                                                            /  \   /  \

                    情况三:node的兄弟节点other为黑色,并且other左子女为红色,other右子女为黑色:

                                            B(parent)                                                                                       B

                                           /      \                             other ->color  = parent ->color                 /   \

                              (node)A        D(other)                   ----------------->                                    A      C

                                       /    \      /   \                       parent ->color = black                                      /  \

                                                 C   E                     other ->left ->color = black                                  D

                                                 /  \   /  \                    右旋(other,root)                                            /  \

                                                                                                                                                                    E

                                                                                                                                                                   /   \

                  情况四:node的兄弟节点other为黑色,且兄弟节点的右孩子是红色的:

                                            B(parent)                                                                                       D

                                           /      \                                  右旋(parent,root)                         /     \

                              (node)A        D(other)                   ----------------->                                    B      E

                                       /    \      /   \                             other ->color= parent ->color           /   \     /   \

                                                 C   E                                                                                    A   C

                                                 /  \   /  \                                                                                  /  \   /  \

                 其具体的代码如下:

Rbtree_node *rb_delete(int data,Rbtree root)   //红黑树的删除操作
{
    Rbtree_node *parent = NULL;
    Rbtree_node *left = NULL;
    Rbtree_node *old = NULL;
    Rbtree_node *node = NULL;
    Rbtree_node *child = NULL;
    int color = RED;

    if((node = rb_search_auxilary(data,root,NULL)) == NULL){
        fprintf(stderr,"value %d is not exist!\n",data);
        return root;
    }

    old = node;
    if(node ->left_child && node ->right_child){
        node = node ->right_child;
        while((left = node ->left_child) != NULL){
            node = left;
        }
        
        child = node ->right_child;
        parent = node ->parent;
        color = node ->color;
        
        if(child != NULL){
            child ->parent = parent;
        }
        if(parent != NULL){
            if(node == parent ->left_child){
                parent ->left_child = child;
            }else{
                parent ->right_child = child;
            }
        }else{
            root = child;
        }

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

        //用子树中的最小值替换待删除节点
        node ->parent = old ->parent;
        node ->left_child = old ->left_child;
        node ->right_child = old ->right_child;
        node ->color = old ->color;

        if(old ->parent != NULL){
            if(old == old ->parent ->left_child){
                old ->parent ->left_child = node;
            }else{
                old ->parent ->right_child = node;
            }
        }else{
            root = node;
        }
        old ->left_child ->parent = node;
        if(old ->right_child != NULL){
            old ->right_child ->parent = node;
        }
    }else{
        //node的左孩子为空
        if(node ->left_child == NULL){
            child = node ->right_child;
        }else if(node ->right_child == NULL){
            child = node ->left_child;
        }
        parent = node ->parent;
        color = node ->color;
        
        if(child != NULL){
            child ->parent = parent;
        }
        
        if(parent != NULL){
            if(node == parent ->left_child){
                parent ->left_child = child;
            }else{
                parent ->right_child = child;
            }
        }else{
            root = child;
        }
    }
    free(old);

    if(color == BLACK){
        root = rb_delete_rebalance(child,parent,root);
    }
    return root;
}
static Rbtree_node *rb_delete_rebalance(Rbtree_node * node,
                  Rbtree_node * parent,Rbtree_node * root)
{
    Rbtree_node *other = NULL;
   
    while((node == NULL || node ->color == BLACK) && node != root){
        if(parent ->left_child == node){
            //找到兄弟节点的位置
            other = parent ->right_child;
            if(other ->color == RED){
                //case1:兄弟节点为红色
                other ->color = BLACK;
                parent ->color = RED;
                root = Right_rotate(parent,root);
                other = parent ->left_child;
            }
            if((other ->left_child == NULL || 
            other ->left_child ->color == BLACK) 
            &&(other ->right_child == NULL || 
            other ->right_child ->color == BLACK)){
                //case2:兄弟节点为黑色,且兄弟节点的两个孩子也是黑色
                other ->color = RED;
                node = parent;
                parent = node ->parent;
            }else{
                //case3:兄弟节点为黑色,且兄弟节点的左孩子为红色,
                //右孩子为黑色
                if(other ->right_child == NULL || 
                other ->right_child ->color == BLACK){
                    if(other ->left_child){
                        other ->left_child ->color = BLACK;
                    }
                    other ->color = RED;
                    root = Right_rotate(other,root);
                    other = parent ->right_child;
                }                
                //case4:兄弟节点为黑色,且兄弟节点的右孩子为红色
                other ->color = parent ->color;
                parent ->color = BLACK;
                if(other ->right_child != NULL){
                    other ->right_child ->color = BLACK;
                }
                root = Left_rotate(parent,root);
                node = root;
                break;
            }
        }else{
            other = parent ->left_child;
            if(other ->color == RED){
                other ->color = BLACK;
                parent ->color = RED;
                root = Right_rotate(parent,root);
                other = parent ->left_child;
            }
            if((other ->left_child == NULL || 
              other ->left_child ->color == BLACK)
            && (other ->right_child == NULL ||
              other ->right_child ->color == BLACK)){
                 other ->color = RED;
                 node = parent;
                 parent = node ->parent;
             }else{
                 if(other ->left_child == NULL ||
                   other ->left_child ->color == BLACK){
                     if(other ->right_child){
                         other ->right_child ->color = BLACK;
                     }
                     other ->color = RED;
                     root = Left_rotate(other,root);
                     other = parent ->left_child;
                 }
                 other ->color = parent ->color;
                 parent ->color = BLACK;
                 if(other ->left_child != NULL){
                     other ->left_child->color = BLACK;
                 }
                 root = Right_rotate(parent,root);
                 node = root;
                 break;
             }    
            
        }
    }
    
    if(node != NULL){
        node ->color = BLACK;
    }

    return root;
}

(四、)红黑树的其他接口:

    rb_inserach(红黑树的查找)、红黑树的遍历和得到红黑树的最大最小值:

Rbtree_node *find_max_node(Rbtree root)        //找到红黑树中的最大值节点
{
    //寻找红黑树最大值节点的方法其实与二叉搜索树的寻找方法一样
    Rbtree_node *p_node = NULL;
    p_node = root;
    while(p_node && p_node ->right_child){
        p_node = p_node ->right_child;
    }
    return p_node;
}

Rbtree_node *find_min_node(Rbtree root)        //找到红黑树中的最小值节点
{
    Rbtree_node *p_node = NULL;
    p_node = root;
    while(p_node && p_node ->left_child){
        p_node = p_node ->left_child;
    }
    return p_node;
}
Rbtree_node *rb_search(int data,Rbtree root)   //返回上述查找过程查找结果
{
    return rb_search_auxilary(data,root,NULL);
}
static Rbtree_node *creat_tree_node(void)
{
    Rbtree_node *node = NULL;
    node = (Rbtree_node *)Malloc(sizeof(Rbtree_node));
    return node;
}
void pre_order_print(Rbtree root)             //先序遍历红黑树
{
    if(root){
        printf("%5d",root ->data);
        pre_order_print(root ->left_child);
        pre_order_print(root ->right_child);
    }
}
void mid_order_print(Rbtree root)              //中序遍历红黑树
{
    if(root){
        mid_order_print(root ->left_child);
        printf("%5d",root ->data);
        mid_order_print(root ->right_child);
    }

}
void last_order_print(Rbtree root)            //后序遍历红黑树
{
    if(root){
        last_order_print(root ->left_child);
        last_order_print(root ->right_child);
        printf("%5d",root ->data);
    }

}

下面我们来看测试程序的书写:

#include <stdio.h>
#include <stdlib.h>
#include "tools.h"
#include "rbtree.h"
#include <math.h>
#include <time.h>

int main(int argc, char **argv)
{
    Rbtree root = NULL; 
    Rbtree_node *node = NULL;   

    int value = 0;
    int i = 0;
    int count = 8;
 
    srand(time(NULL));

    for(i = 1; i < count; ++i){
        value = i;
        if((root = rb_insert(value, root))){
            printf("insert value %d success!\n", value);
        }else{
            printf("insert value %d failed!\n", value);
        }
        printf("root value :%d\n", root->data);
        if((node = rb_search(value, root))){
            printf("found %d !\n", value);
        }else{
            printf("not found %d!\n", value);
        }
        pre_order_print(root);
        printf("\n");
       
 #if 1 
        if(i % 3 == 0){
            if((root = rb_delete(value, root))){
                printf("delete %d success!\n", value);
            }else{
                printf("delete %d failed!\n", value);
            }
        }
#endif
    }
    return 0;
}

接下来我们来看程序的执行结果:


















             
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值