红黑树

本文的大部分内容是转载过来的,只有少部分是自己以更通俗的方式讲出来的。所以这篇文章设为转载。

什么是红黑树?
一棵自平衡的二叉查找树
那么什么是自平衡的二叉查找树呢,其实就是在插入、删除、修改等操作执行的时候为了符合自己的性质,执行的一系列自平衡操作。

那么我们先来说一下红黑树的性质:

性质
每个结点是黑色或红色的
根结点是黑色的
所有的叶子(NIL)结点都是黑色的。这里的NIL也就是空的意思,是没有数据的结点。
红色结点的孩子结点是且仅是黑色的。
从任一结点开始,到它的任何叶子结点,所经过的黑色结点数目相同。

我们一定要仔细的看一下这些性质。
4中红色结点的孩子必须是黑色的;但是可没有说黑色结点的孩子一定是红色。
5中确保没有一条路径会比其他路径长出两倍。
在这里插入图片描述
定理:一棵含有n个节点的红黑树的高度至多为2log(n+1).

左旋右旋

左旋

在这里插入图片描述
在图中 已知α < X < β < Y < γ,所以必须始终保持这样的规则。我们的目的是为了将Y变为该子树的根结点,所以结果如上图所示。
在这里插入图片描述
这里的代码都是从文章最后的原文中借鉴过来的。 宏定义如下。

#define rb_parent(r)   ((r)->parent)
#define rb_color(r) ((r)->color)
#define rb_is_red(r)   ((r)->color==RED)
#define rb_is_black(r)  ((r)->color==BLACK)
#define rb_set_black(r)  do { (r)->color = BLACK; } while (0)
#define rb_set_red(r)  do { (r)->color = RED; } while (0)
#define rb_set_parent(r,p)  do { (r)->parent = (p); } while (0)
#define rb_set_color(r,c)  do { (r)->color = (c); } while (0)
/* 
 * 对红黑树的节点(x)进行左旋转
 *
 * 左旋示意图(对节点x进行左旋):
 *      px                              px
 *     /                               /
 *    x                               y                
 *   /  \      --(左旋)-->           / \                #
 *  lx   y                          x  ry     
 *     /   \                       /  \
 *    ly   ry                     lx  ly  
 *
 *
 */
template <class T>
void RBTree<T>::leftRotate(RBTNode<T>* &root, RBTNode<T>* x){//root为这棵红黑树的根结点,x是要左旋的支点
    RBTNode<T> *y = x->right;//很明显将x的左孩子设为y,我们就是要以y为这棵子树的根结点。
    x->right = y->left;//y的左孩子赋值给x的右孩子,切断了x和y之间的联系。
    if (y->left != NULL) y->left->parent = x;//如果ly不为null,我们设置ly的父结点为x
    y->parent = x->parent;//将x的父结点赋值给y的父结点,因为x被y替代了,要更新y的信息
    if (x->parent == NULL){//如果x的父结点为NULL 说明x是这棵红黑树的根结点
        root = y;
    }else{//此时说明x不是根结点
        if (x->parent->left == x) x->parent->left = y;//如果x是父结点的左孩子,那么就把y赋值给x的父结点的左结点。
        else x->parent->right = y;//否则就把y赋值给x的父结点的右结点。
    }
    y->left = x;//x变成y的左结点
    x->parent = y;//y变成x的父结点
}

右旋

右旋与左旋类似。

添加操作

首先,红黑树是一棵二叉查找树。那么我们很容易可以找到插入的位置。但是为了满足红黑树的性质。我们就需要进行自平衡。

那么什么时候需要进行自平衡呢?

先把性质请过来
性质
1每个结点是黑色或红色的
2根结点是黑色的
3所有的叶子(NIL)结点都是黑色的。
4红色结点的孩子结点是且仅是黑色的。
5从任一结点开始,到它的任何叶子结点,所经过的黑色结点数目相同。

我们给插入结点着色为红色,(如果设为黑色,就会导致根到叶子的路径上有一条路上,多一个额外的黑节点,违反性质5,这个是很难调整的。但是设为红色节点后,可能会导致出现两个连续红色节点的冲突,违反性质4,那么可以通过颜色调换和树旋转来调整。)

template <class T>
void RBTree<T>::insert(RBTNode<T>* &root, RBTNode<T>* node){//node即为插入结点
    RBTNode<T> *y = NULL;
    RBTNode<T> *x = root;
    while (x != NULL){//这一步是找到node所应该插入的位置
        y = x;//y结点是node结点的父结点
        if (node->key < x->key) x = x->left;
        else x = x->right;
    }
    node->parent = y;
    if (y!=NULL){
    	//判断node应该放在y的左边还是右边
        if (node->key < y->key) y->left = node;
        else y->right = node;
    }//如果y为null,说明红黑树中无元素,node是第一个,设置为root
    else root = node;
    node->color = RED;//node的颜色设为红色
    insertFixUp(root, node);//进行自平衡操作
}
//########################################################################################
template <class T>
void RBTree<T>::insert(T key){
    RBTNode<T> *z=NULL;
    // 如果新建结点失败,则返回。
    if ((z=new RBTNode<T>(key,BLACK,NULL,NULL,NULL)) == NULL)
        return ;
    insert(mRoot, z);
}
//########################################################################################
template <class T>
void RBTree<T>::insertFixUp(RBTNode<T>* &root, RBTNode<T>* node){
    RBTNode<T> *parent, *gparent;
    //★★★★★
    //1. 如果没有父结点的话,说明node为根,所以不需要进入循环,直接设置root为黑色即可
    //2. 如果父结点为黑色符合性质4(因为插入红色结点,可能会违反4,父结点为黑色就不可能违反),皆大欢喜
    //3. 如果父结点为红色那么此时已经违反了性质4,下面分情况考虑
    while ((parent = rb_parent(node)) && rb_is_red(parent)){//parent为node的父结点,如果父结点为红色
        gparent = rb_parent(parent);//找到祖父结点
        if (parent == gparent->left){//如果父结点是祖父结点的左孩子结点
            {
                RBTNode<T> *uncle = gparent->right;//找到叔叔结点(即祖父结点的右孩子)
                //★★★★★3.1 node结点是红色,parent是红色,uncle是红色
                if (uncle && rb_is_red(uncle)){//如果叔叔结点不为null且叔叔结点是红色,★★★★★如下图图一所示
                    rb_set_black(uncle);
                    rb_set_black(parent);
                    rb_set_red(gparent);
                    node = gparent;//因为原来祖父结点肯定为黑色,你现在把它着色成红色了,那万一祖父的爹是红色呢。  又成了一开始的状况了。 所以循环进行判断就行了。
                    continue;
                }
            }
            //此时叔叔为null或者为黑色,但是由于parent是红色,那么gparent只能是黑色,由于性质5,那么叔叔只能是null
            //★★★★★3.2 node结点是红色,parent是红色,uncle是null★★★★★如下图二所示
            if (parent->right == node){ //如果node是parent的右孩子
                RBTNode<T> *tmp;
                leftRotate(root, parent);//左旋
                tmp = parent;
                parent = node;//交换位置
                node = tmp;//经过这一轮左旋交换之后,就变成了node是parent的左孩子
            }
            rb_set_black(parent);//重新着色一下
            rb_set_red(gparent);
            rightRotate(root, gparent);//右旋
            //这时候parent变成了黑色,红黑树已经平衡。
            //下面的代码类似就不作解释了
        } 
        else{
            {
                RBTNode<T> *uncle = gparent->left;
                //3.3
                if (uncle && rb_is_red(uncle)){
                    rb_set_black(uncle);
                    rb_set_black(parent);
                    rb_set_red(gparent);
                    node = gparent;
                    continue;
                }
            }
            //3.4
            if (parent->left == node){
                RBTNode<T> *tmp;
                rightRotate(root, parent);
                tmp = parent;
                parent = node;
                node = tmp;
            }
            rb_set_black(parent);
            rb_set_red(gparent);
            leftRotate(root, gparent);
        }
    }
    // 将根节点设为黑色
    rb_set_black(root);
}

这是图一

在这里插入图片描述

这是图二

删除

首先,通过二叉查找树的性质进行查找并删除,然后进行相应的自平衡操作。
我们要删除这个结点,为了保持二叉查找树的性质,就需要用后继结点替换掉这个结点。
如果删除结点是黑色,那么我们肯定需要进行自平衡。
有三种情况

  1. node没有儿子,即子结点,那么就直接删除node。
  2. node有一个儿子,将儿子直接代替node。
  3. node有两个儿子,找到这个结点的后继结点rnode,用rnode代替node,且颜色是不能变化的,此时删除掉原来位置的rnode,此时就变成可第二种或第一种情况(解释如下)。

后继结点有几个重点要掌握一下:
后继结点:要么是右子树中的最小值对应的结点;要么是左父亲的第一个右父亲(当然没有这种说法,也就是左父亲表示node是父亲的右孩子,在下图中表达,哪一个是后继结点呢?自己找一找吧,找不出来可以填几个数字看看)。
在上述两种情况下:
第一种情况,node不可能有左孩子,第二种情况,node不可能有右孩子。(为什么呢?这个不难考虑,所以就不说了)

但是第二种情况下node最多只有一个左孩子,所以删除的第三种情况不包含这个。

所以删除时候的第三种情况就能解决了。
在这里插入图片描述

template <class T>
void RBTree<T>::remove(RBTNode<T>* &root, RBTNode<T> *node){//node为删除结点
    RBTNode<T> *child, *parent;
    RBTColor color;
    //★★★★★node的左右结点都不为null的时候,我们就要找到后继结点。第3种情况
    if ( (node->left!=NULL) && (node->right!=NULL) ) {
        RBTNode<T> *replace = node;
        replace = replace->right;
        while (replace->left != NULL)//找到node的后继结点我们称为rnode
            replace = replace->left;
        if (rb_parent(node)){
            if (rb_parent(node)->left == node)//如果node是左结点,则用rnode代替parent的左孩子
                rb_parent(node)->left = replace;
            else//右孩子同理
                rb_parent(node)->right = replace;
        }else//★★★★★删除的node是根结点
            root = replace;
        child = replace->right;
        parent = rb_parent(replace);
        color = rb_color(replace);
        if (parent == node){//如果rnode的父结点是node结点的话
            parent = replace;//直接替换node
        }else{//这时候就转变成了用rnode的右孩子代替原来位置的rnode
            if (child)//如果child不为null
                rb_set_parent(child, parent);//把rnode的父结点parent 作为 孩子的父结点
            parent->left = child;//rnode肯定是parent的左孩子,所以把parent的左孩子设为child
            replace->right = node->right;//新的rnode的右孩子就变成删除结点的右孩子
            rb_set_parent(node->right, replace);
        }
        replace->parent = node->parent;
        replace->color = node->color;//这里一定要把颜色转变了。
        replace->left = node->left;
        node->left->parent = replace;
        if (color == BLACK)
            removeFixUp(root, child, parent);
        delete node;
        return ;
    }
    //★★★★★下面的情况是代表上述的第1.2种情况,直接用子结点替换掉node结点即可。
    if (node->left !=NULL) //如果左孩子不为null
        child = node->left;
    else //右孩子不为null或者两个都为null
        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//node是根结点
        root = child;
    //如果node的颜色是黑色,就进行自平衡
    if (color == BLACK)
        removeFixUp(root, child, parent);
    delete node;
}
//########################################################################################
template <class T>
void RBTree<T>::remove(T key){
    RBTNode<T> *node; 
    if ((node = search(mRoot, key)) != NULL)
        remove(mRoot, node);
}
//########################################################################################
//到了这里就进行自平衡操作了。我们先看一下红黑树变成了什么样子

在这里插入图片描述
rnode替换node
在这里插入图片描述
这种情况已经不符合红黑树的性质了。
rnode代替node使得以parent为根的子树破坏了性质5。左子树比右子树到叶子结点的黑色结点数量小一。我们的目的就是让左子树的黑色结点数加一。
接下来我们尝试去自平衡这两棵树。

//此时的node是上述parent左右子树中数量小的那个棵的根结点
//我们设node的兄弟结点为bnode
template <class T>
void RBTree<T>::removeFixUp(RBTNode<T>* &root, RBTNode<T> *node, RBTNode<T> *parent){
    RBTNode<T> *other;
    //(node为null或者node是黑色) 并且node不是root
    while ((!node || rb_is_black(node)) && node != root){
        if (parent->left == node){
            other = parent->right;
            if (rb_is_red(other)){//如果bnode是红色
                rb_set_black(other);
                rb_set_red(parent);
                leftRotate(root, parent);//左旋使得bnode的左孩子变成parent的右孩子。
                other = parent->right;//将parent的右孩子赋值给bnode
            }
            //如果现在(bnode的左孩子为null或者是黑色的) 并且 (bnode的右孩子为null或者是黑色的)
            //没有红色孩子
            if ((!other->left || rb_is_black(other->left)) &&
                (!other->right || rb_is_black(other->right))){
                rb_set_red(other);//bnode
                node = parent;
                parent = rb_parent(node);
            }else{
                if (!other->right || rb_is_black(other->right)){ 
                    rb_set_black(other->left);
                    rb_set_red(other);
                    rightRotate(root, other);
                    other = parent->right;
                }
                rb_set_color(other, rb_color(parent));
                rb_set_black(parent);
                rb_set_black(other->right);
                leftRotate(root, parent);
                node = root;
                break;
            }
        } else{
            other = parent->left;
            if (rb_is_red(other)){
                rb_set_black(other);
                rb_set_red(parent);
                rightRotate(root, parent);
                other = parent->left;
            }
            if ((!other->left || rb_is_black(other->left)) &&
                (!other->right || rb_is_black(other->right))){
                rb_set_red(other);
                node = parent;
                parent = rb_parent(node);
            }else{
                if (!other->left || rb_is_black(other->left)){  
                    rb_set_black(other->right);
                    rb_set_red(other);
                    leftRotate(root, other);
                    other = parent->left;
                }
                rb_set_color(other, rb_color(parent));
                rb_set_black(parent);
                rb_set_black(other->left);
                rightRotate(root, parent);
                node = root;
                break;
            }
        }
    }
    if (node)
        rb_set_black(node);
}

我们的目的是将parent的左子树上的黑色结点数加一。
经过变色左旋变为下图第一棵树所示。
在这里插入图片描述
感觉自己的删除这一块的自平衡有点不懂。所以有很多地方没写注释。
下面的借鉴网站的总结:
假设要删除的结点是node,代替node的是rnode,rnode的兄弟结点是bnode,bnode的子结点为bl和br,rnode的父亲是parent。
要始终记住我们的目的就是在rnode所在的分支增加一个黑色结点,使得parent到叶子结点的黑色结点数相等。

  1. 需要在rnode所在的子树增加一个黑结点,bnode是红色(所以parent为黑色,bl和br都是黑色)
    在这里插入图片描述
    1. 以parent为支点左旋
      此时br所在的路径黑色结点减一了,而其他没变。
      在这里插入图片描述

    2. 将parent变为红色,bnode变为黑色
      此时bl和br都是原来的样子,rnode也是缺一个,设bl的左孩子bll,右孩子blr
      如果bll和blr没有红色那么就执行3,否则执行4。

    3. bll和blr都为黑色,把parent设为黑色,bl设为红色。那么rnode所在分支就加了一个黑色结点。平衡结束。
      在这里插入图片描述

    4. 如果bll和blr至少有一个红色结点
      如果blr为黑色或者为null,那么先把bl设为红色,bll设为黑色,以bl右旋,然后把parent的右指针指向bl,新bl(原来的bll)的颜色设为parent的颜色,parent和bl的右结点(原来的bl)都设为黑色,然后以parent为支点左旋。仔细观察计算一下,其实此时性质5已经满足了。(主要看一下每个结点到其叶子结点的黑结点数量)
      在这里插入图片描述
      在这里插入图片描述

  2. 需要在rnode所在的子树增加一个黑结点,bnode是黑色,parent为黑色,bl和br都为黑色
  3. 需要在rnode所在的子树增加一个黑结点,bnode是黑色,parent为黑色,bl是红色,br是黑色
  4. 需要在rnode所在的子树增加一个黑结点,bnode是黑色,parent为黑色,bl是任意颜色,br是红色

下面几个以后补吧,实现写不动了,而且也不太理解。还请看原文吧。

参考文章:
红黑树(一)之 原理和算法详细介绍

红黑树(四)之 C++的实现

漫画:什么是红黑树?

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值