数据结构-搜索树-红黑树详解

红黑树(RedBlack)

声明:本文章源码来自于清华大学邓俊辉老师,文中插图也为其ppt中图片
源码
由于红黑树与B树的渊源较深,阅读此文前请先了解B-树的原理.
红黑树是一种相较于AVL树,BST树,Splay树均有所不同的搜索树结构,其突出特点为无论insert/remove,其时间复杂度均不超过 O ( 1 ) O(1) O(1).
其结构为:首先先抛出具象的规则:
1)树根:必为黑色
2)外部节点:均为黑色
3)其余节点:若为红,则只能有黑孩子 //红之子、之父必黑,仅对于红结点而言.
4)外部节点到根:途中黑节点数目(黑深度)相等
外部结点为人为设定便于理解的实际上不存在(NULL),目的是使之成为真二叉树.

上述的说明也许还是有点抽象所以对于此举出实例来说明.
在这里插入图片描述
然后我们对其做一个提升变换,将所有的红结点提升至其父亲的统一高度,其结果为:在这里插入图片描述

还是不够具象,我依据上述结构,可以联想出一种之前学过的结构B-树((2,4)树).红黑树被提升后的节点模型,有四种其情况如下
在这里插入图片描述
在这里我们还是没有清楚的明白,这些结构的意义在于,这是个悬念后续展开,先解释其数据的结构.

template <typename T> class RedBlack : public BST<T> { //RedBlack树模板类
protected:
    void solveDoubleRed ( BinNodePosi(T) x ); //双红修正
    void solveDoubleBlack ( BinNodePosi(T) x ); //双黑修正
    int updateHeight ( BinNodePosi(T) x ); //更新节点x的高度
public:
    BinNodePosi(T) insert ( const T& e ); //插入(重写)
    bool remove ( const T& e ); //删除(重写)
    // BST::search()等其余接口可直接沿用
};

基本接口:插入(Insert)

1.按照BST规则插入关键码e;
2.除非是首个节点(根),x的父亲p = x->parent必存在.首先将x染红 x->color = isRoot(x)?B:R;
3.红黑树的四个规则中1,2,4依然满足,但是3不见得满足,有可能出现双红(double-red)现象出现,则需要分情况处理.

  • 考察:祖父 g = p->parent;

    ​ 叔父 u = (p == g->lc) ? g->rc :g->lc

  • 以下视u的颜色,分两种情况处理.

RR-1: u->color == B:

有zig-zig,zig-zag;zag-zag,zag-zig四种情况
现给出两种情况,另外两种情况基本对称所以省略

在这里插入图片描述

由图可知实际的情况中出现了,两个红色结点在一起的情况.虽然没有违反B树的上限规则,但是并不满足红黑树的要求,所以我们需要作出修改.

**我们希望的是 红黑红 的情况出现所以需要将中间的点变为黑色,所以需要进行重新的染色.而且要求中心点为黑色,我梦可以借鉴之前学习过的"3+4重构"的方法,将四种情况重构成如下图的情况 **

在这里插入图片描述将b染黑,a or c 染红.

RR-2: u->color == R:**有zig-zig,zig-zag;zag-zag,zag-zig四种情况

同样仅给出两种情况,其他由于对称不再给出

在这里插入图片描述

这里情况就较为复杂,应为对应到B树,该超级节点就为5阶超过了B树的上限规定.所以对于此借鉴B树对于上溢情况的处理,我们将对应的[m/2]位置的节点上溢,同时将p与u转黑,g转红.对应B-树,等效于

  • 节点分裂
  • 关键码g上升一层
    在这里插入图片描述

由于上溢可能会出现会向上传递—亦即,g与parent(g)再次构成双红.

果真如此,可

  • 等效地将g视作新插入的节点
  • -区分以上两种情况,如法处置

知道所有的条件满足或者抵达树根,假设到达树根则,强行将g转为黑色,整树(黑)的高度加一.

接口的代码实现为:

template <typename T>
BinNodePosi(T) RedBlack<T>::insert(const T &e)
{
    BinNodePosi(T)& x = search(e);//bst::search()
    if(x)
        return x; //确认目标节点不存在
    //创建红节点x,以_hot为父,黑高度 = -1
    x = new  BinNode<T>(e,_hot,NULL,NULL,-1);
    _size++;
    //如有必要,需要做双红修成,再返回插入的节点
    BinNodePosi(T) xOld = x;
    solveDoubleRed(x);
    return xOld;
}//无论原树中是否存有e,返回时总有x->data == e

注意这里的代码行4和行8 这里对于x进行了两次赋值,第一次是进行e的搜寻,由于插入–原本结构中没有e,所以search()返回的是NULL,但是_hot保存了搜寻点的parent.所以虽说返回值为NULL,但_hot值改变了.

双红情况的处理为:

template <typename T>
void RedBlack<T>::solveDoubleRed(BinNodePosi(T) x)
{//x当前必为红
    if(IsRoot(*x))//若以递归转至树根,则将其转黑,整树黑高度也随之递增
    {
        _root->color = RB_BLACK;
        _root->height++;
        return;
    }
    //考察p,g,u
    BinNodePosi(T) p = x->parent;
    BinNodePosi(T) g = p->parent;
    BinNodePosi(T) u = (p == g->lc)?g->rc:g->lc;
    if(p->color == RB_BLACK)//若p为黑,则可以终止调整
        return ;
    else if(u->color == RB_BLACK || u == NULL)
    {//若uncle为黑或者为空则为情况1,旋转+染色(3+4重构)
        if(IsLChild(*x) == IsLChild(*p))
            p->color = RB_BLACK;
        else
            x->color = RB_BLACK;
        g->color = RB_RED;
        BinNodePosi(T) gg = g->parent;
        BinNodePosi(T) r = FromParentTo(*g) = rotateAt(x);
        r->parent = gg;
    }
    else
    {
        p->color = RB_BLACK;
        p->height++;
        u->color = RB_BLACK;
        u->height++;
        if(!IsRoot(*g))
            g->color = RB_RED;
        solveDoubleRed(g);
    }
}

代码的解释:

1.考察的是x,parent,grandparent,uncle,四种情况

2.假设parent为黑,结束循环

3.RR-1;

#define FromParentTo(x) /*来自父亲的引用*/ \
        ( IsRoot(x) ? _root : ( IsLChild(x) ? (x).parent->lc : (x).parent->rc ) )

这句话表示的是是否来自于父亲的引用.验证当前节点是否为root和是否为左右孩子,主要是验证是否为根节点.

会经过两次染色和1~2次旋转.

3.RR-2:所有情况均三次染色(p,u,g)

在这里插入图片描述

基本接口:删除(Remove)

对于AVL树而言删除操作,由于可能会造成O(logn)的操作,所以我们来看看,对于红黑树而言,其删除操作是怎样进行的.

对于B树而言以曲为直,从B树的角度看RB树是一种极好的思想方法.

问题的引入:于插入类似的情况对于删除操作,一开始借鉴的是BST的删除算法,r=removeAt(x,_hot),其基本方法是找到当前节点的后继,使之代替当前节点,而后删除.来进行验证这样的删除操作是否符合红黑树的规则要求.可以预见的这会是很多种的情况,我们先讨论一下单纯进行remove操作之后,情况是什么样的,显而易见规则1,2是满足要求的,但是3,4就不一定了.

1.x和r其一为红色(删除前)

​ case1:若x为红,则条件3,4自然满足

​ case2:若r为红,则令其与x交换颜色.

在这里插入图片描述

2.双黑缺陷:

假设x和r均黑,若是直接删除x则,全树的黑高度将不再统一,如图所示:

在这里插入图片描述
新树,中考察:

  • r的父亲(c’为新树) :p = r->parent
  • r的兄弟:s= (r == p->lc) ? p->lc:p->rc;

以下对于不同的情况做处理:

由于存在双黑(K为NULL从未实际存在)情况,所以联想到b树,双黑,可以将原树的x想为二阶结点,而后被删除,出现下溢,则这个时候对应到B树就有了两种解决情况:1.旋转2,合并.

1.BB-1:s为黑,且至少有一个红孩子t

将原树lifting,则为左下角情况,可想而知解决方案为旋转,**将s上移,p右下移到第三种情况,同时为了保持上面情况不变,s继承p的颜色,而将t变为黑色.**实际操作为:

  • “3+4”重构:t、s、p重命名为a、b、c
  • r保持黑;a和c染黑;b继承p的原色
    在这里插入图片描述

上述为zig-zig,zig-zag情况是类似的

既然上述为左兄弟够胖,假设左兄弟不够胖怎么办,与r类似—即s的两个孩子均为黑则,我们需要采取合并的方法.合并这个也是有说法的,由于p未定,所以分情况讨论.1,p为红:BB-2R 2,p为黑:BB-2B

2.BB-2R:s为黑,且两个孩子均为黑;p为红

所以由于p为红色,B树中:p对应的超级节点为4阶,那下去一个不影响结构,所以这里采用的是B树的合并操作,流程如图所示,实际操作为:

  • r保持黑,s为红;p转为黑色.(仅为颜色操作)

在这里插入图片描述
3.BB-2B:s为黑,两个孩子为黑,且p也为黑色

由于这样的情况每个超级节点均为二阶,所以进行合并后上层也需要继续向上修正(黑高度变小了),以保持规则.但是如图可知,实际操作仅为:

  • 将s改为红色.

在这里插入图片描述

所幸最坏也不过染色 O ( log ⁡ n ) O(\log n) O(logn)次.

4.BB-3:s为红色(其孩子均为黑–规则)

前面都是以s为黑色展开的但是到这里,以s为红色,这一种情况由于黑高度的问题,在这里我们需要先进行一些修改让其转为其他的情况,还是从B树上来,我们直接将s和p的颜色进行调换,这样我们再还原到红黑树,p就有一个黑孩子,这样情况就转变为了BB-1/BB-2R(P为红),所以在经过一次操作即可恢复正常.实际的红黑操作为:

  • zag§或zig§;红s转黑,黑p转红.

在这里插入图片描述
实现代码为

 /******************************************************************************************
  * RedBlack双黑调整算法:解决节点x与被其替代的节点均为黑色的问题
  * 分为三大类共四种情况:
  *    BB-1 :2次颜色翻转,2次黑高度更新,1~2次旋转,不再递归
  *    BB-2R:2次颜色翻转,2次黑高度更新,0次旋转,不再递归
  *    BB-2B:1次颜色翻转,1次黑高度更新,0次旋转,需要递归
  *    BB-3 :2次颜色翻转,2次黑高度更新,1次旋转,转为BB-1或BB2R
  ******************************************************************************************/
template <typename T> 
void RedBlack<T>::solveDoubleBlack ( BinNodePosi(T) r ) 
{
    BinNodePosi(T) p = r ? r->parent : _hot; if ( !p ) return; //r的父亲
    BinNodePosi(T) s = ( r == p->lc ) ? p->rc : p->lc; //r的兄弟
    if ( IsBlack ( s ) ) 
    { //兄弟s为黑
       BinNodePosi(T) t = NULL; //s的红孩子(若左、右孩子皆红,左者优先;皆黑时为NULL)
       if ( IsRed ( s->rc ) ) t = s->rc; //右子
       if ( IsRed ( s->lc ) ) t = s->lc; //左子
       if ( t ) { //黑s有红孩子:BB-1
       RBColor oldColor = p->color; //备份原子树根节点p颜色,并对t及其父亲、祖父
       // 以下,通过旋转重平衡,并将新子树的左、右孩子染黑
       BinNodePosi(T) b = FromParentTo ( *p ) = rotateAt ( t ); //旋转
       if ( HasLChild ( *b ) ) { b->lc->color = RB_BLACK; updateHeight ( b->lc ); } //左子
       if ( HasRChild ( *b ) ) { b->rc->color = RB_BLACK; updateHeight ( b->rc ); } //右子
       b->color = oldColor; updateHeight ( b ); //新子树根节点继承原根节点的颜色
       } 
       else 
       { //黑s无红孩子
          s->color = RB_RED; s->height--; //s转红
          if ( IsRed ( p ) ) 
          { //BB-2R
             p->color = RB_BLACK; //p转黑,但黑高度不变
          } 
          else 
          { //BB-2B
             p->height--; //p保持黑,但黑高度下降
             solveDoubleBlack ( p ); //递归上溯
          }
       }
     } 
    else 
    { //兄弟s为红:BB-3
       s->color = RB_BLACK; p->color = RB_RED; //s转黑,p转红
       BinNodePosi(T) t = IsLChild ( *s ) ? s->lc : s->rc; //取t与其父s同侧
       _hot = p; FromParentTo ( *p ) = rotateAt ( t ); //对t及其父亲、祖父做平衡调整
       solveDoubleBlack ( r ); //继续修正r处双黑——此时的p已转红,故后续只能是BB-1或BB-2R
    }
 }
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值