数据结构 | 第8章 - 红黑树(下)

125 阅读6分钟

8.3.4 节点删除算法

  • 节点删除与双黑现象

    代码8.18 红黑树remove()接口:

     0001 template <typename T> bool RedBlack<T>::remove ( const T& e ) { //从红黑树中删除关键码e
     0002    BinNodePosi<T> & x = search ( e ); if ( !x ) return false; //确认目标存在(留意_hot的设置)
     0003    BinNodePosi<T> r = removeAt ( x, _hot ); if ( ! ( --_size ) ) return true; //实施删除
     0004 // assert: _hot某一孩子刚被删除,且被r所指节点(可能是NULL)接替。以下检查是否失衡,并做必要调整
     0005    if ( ! _hot ) //若刚被删除的是根节点,则将其置黑,并更新黑高度
     0006       { _root->color = RB_BLACK; updateHeight ( _root ); return true; }
     0007 // assert: 以下,原x(现r)必非根,_hot必非空
     0008    if ( BlackHeightUpdated ( *_hot ) ) return true; //若所有祖先的黑深度依然平衡,则无需调整
     0009    if ( IsRed ( r ) ) //否则,若r为红,则只需令其转黑
     0010       { r->color = RB_BLACK; r->height++; return true; }
     0011 // assert: 以下,原x(现r)均为黑色
     0012    solveDoubleBlack ( r ); return true; //经双黑调整后返回
     0013 } //若目标节点存在且被删除,返回true;否则返回false
    

    如代码8.18所示,为删除关键码e,首先调用标准接口BST::search(e),查找目标节点x。 若查找成功,则调用内部接口removeAt(x)实施删除。按照7.2.6节对该接口所做的语义约定,其间无论是否做过一次节点交换,均以r指向实际被删除节点x的接替者,p = _hot为其父亲,且 r 的兄弟为外部节点w = NULL。

    不难验证,此时红黑树的前两个条件继续满足,但后两个条件却未必。

    image-20220724174721796.png

    图(c')所示局部子树的黑高度将会降低一个单位。

    被删除节点x及其替代者r同为黑色的此类情况,称作“双黑”(double black)现象(对应图(c)) 。此时,需从 r 出发调用solveDoubleBlack(r)算法予以修正。

    将原黑节点x的兄弟记作s;x的父亲记作p,(其颜色若不确定以八角形示意)。以下视s和p颜色的不同组合,按四种情况分别处置(均存在其对称情况)。

  • 双黑修正(BB-1)

    节点s、x为黑,且s有一孩子为红。

    image-20220723202751126.png

    解决方法:对节点t、s和p实施“3 + 4”重构。

  • 双黑修正(BB-2-R)

    节点s及其两个孩子均为黑色,且p为红色。

image-20220723202816114.png

解决方法:

从B-树的角度:按照8.2.8节的B-树平衡算法,将关键码p取出并下降一层,然后以之为“粘合剂”将原左、右孩子合并为一个节点。

从红黑树角度看:s 和 p 颜色互换。

  • 双黑修正(BB-2-B)

    节点s及其两个孩子均为黑色,且p为黑色。

    image-20220724180052436.png

    解决方法:

    从B-树角度:将下溢节点与其兄弟合并。

    从红黑树角度:节点s由黑转红。

    可能出现的问题:虽然红黑树的所有条件在局部得到恢复,但可能导致下溢的传递。

  • 双黑修正(BB-3)

    节点s为红色。

image-20220723202837544.png

解决方法:

从B-树的角度:令关键码s与p互换颜色。

从红黑树角度:以节点p为轴做一次旋转,并交换节点s与p的颜色。

存在的问题:双黑缺陷依然存在(子树 r 的黑高度并未复原),且缺陷位置的高度也并未上升。

PS:顺便一提,双黑修正就是为了让节点被删后子树 r 黑高度不变,进而满足条件(4)

进一步解决:转为(BB-1)或(BB-2-R)

  • 双黑修正的复杂度

    image-20220723202901312.pngimage-20220724181310521.png

    双黑修正过程总共耗时不超过O(logn)。即便计入此前的关键码查找和节点摘除操作,红黑树的节点删除操作总是可在O(logn)时间内完成。

    纵览各种情况,不难确认:一旦在某步迭代中做过节点的旋转调整,整个修复过程便会随即完成。因此与双红修正一样,双黑修正的整个过程,也仅涉及常数次的拓扑结构调整操作。

    这一有趣的特性同时也意味着,在每次删除操作之后,拓扑联接关系有所变化的节点绝不会超过常数个——这一点与AVL树(的删除操作)完全不同,也是二者之间最本质的一项差异

  • 双黑修正算法的实现

     0001 /******************************************************************************************
     0002  * RedBlack双黑调整算法:解决节点x与被其替代的节点均为黑色的问题
     0003  * 分为三大类共四种情况:
     0004  *    BB-1 :2次颜色翻转,2次黑高度更新,1~2次旋转,不再递归
     0005  *    BB-2R:2次颜色翻转,2次黑高度更新,0次旋转,不再递归
     0006  *    BB-2B:1次颜色翻转,1次黑高度更新,0次旋转,需要递归
     0007  *    BB-3 :2次颜色翻转,2次黑高度更新,1次旋转,转为BB-1或BB2R
     0008  ******************************************************************************************/
     0009 template <typename T> void RedBlack<T>::solveDoubleBlack ( BinNodePosi<T> r ) {
     0010    BinNodePosi<T> p = r ? r->parent : _hot; if ( !p ) return; //r的父亲
     0011    BinNodePosi<T> s = ( r == p->lc ) ? p->rc : p->lc; //r的兄弟
     0012    if ( IsBlack ( s ) ) { //兄弟s为黑
     0013       BinNodePosi<T> t = NULL; //s的红孩子(若左、右孩子皆红,左者优先;皆黑时为NULL)
     0014       if ( IsRed ( s->rc ) ) t = s->rc; //右子
     0015       if ( IsRed ( s->lc ) ) t = s->lc; //左子
     0016       if ( t ) { //黑s有红孩子:BB-1
     0017          RBColor oldColor = p->color; //备份原子树根节点p颜色,并对t及其父亲、祖父
     0018       // 以下,通过旋转重平衡,并将新子树的左、右孩子染黑
     0019          BinNodePosi<T> b = FromParentTo ( *p ) = rotateAt ( t ); //旋转
     0020          if ( HasLChild ( *b ) ) { b->lc->color = RB_BLACK; updateHeight ( b->lc ); } //左子
     0021          if ( HasRChild ( *b ) ) { b->rc->color = RB_BLACK; updateHeight ( b->rc ); } //右子
     0022          b->color = oldColor; updateHeight ( b ); //新子树根节点继承原根节点的颜色
     0023       } else { //黑s无红孩子
     0024          s->color = RB_RED; s->height--; //s转红
     0025          if ( IsRed ( p ) ) { //BB-2R
     0026             p->color = RB_BLACK; //p转黑,但黑高度不变
     0027          } else { //BB-2B
     0028             p->height--; //p保持黑,但黑高度下降
     0029             solveDoubleBlack ( p ); //递归上溯
     0030          }
     0031       }
     0032    } else { //兄弟s为红:BB-3
     0033       s->color = RB_BLACK; p->color = RB_RED; //s转黑,p转红
     0034       BinNodePosi<T> t = IsLChild ( *s ) ? s->lc : s->rc; //取t与其父s同侧
     0035       _hot = p; FromParentTo ( *p ) = rotateAt ( t ); //对t及其父亲、祖父做平衡调整
     0036       solveDoubleBlack ( r ); //继续修正r处双黑——此时的p已转红,故后续只能是BB-1或BB-2R
     0037    }
     0038 }
    

“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 16 天,点击查看活动详情