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。
不难验证,此时红黑树的前两个条件继续满足,但后两个条件却未必。
图(c')所示局部子树的黑高度将会降低一个单位。
被删除节点x及其替代者r同为黑色的此类情况,称作“双黑”(double black)现象(对应图(c)) 。此时,需从 r 出发调用solveDoubleBlack(r)算法予以修正。
将原黑节点x的兄弟记作s;x的父亲记作p,(其颜色若不确定以八角形示意)。以下视s和p颜色的不同组合,按四种情况分别处置(均存在其对称情况)。
-
双黑修正(BB-1)
节点s、x为黑,且s有一孩子为红。
解决方法:对节点t、s和p实施“3 + 4”重构。
-
双黑修正(BB-2-R)
节点s及其两个孩子均为黑色,且p为红色。
解决方法:
从B-树的角度:按照8.2.8节的B-树平衡算法,将关键码p取出并下降一层,然后以之为“粘合剂”将原左、右孩子合并为一个节点。
从红黑树角度看:s 和 p 颜色互换。
-
双黑修正(BB-2-B)
节点s及其两个孩子均为黑色,且p为黑色。
解决方法:
从B-树角度:将下溢节点与其兄弟合并。
从红黑树角度:节点s由黑转红。
可能出现的问题:虽然红黑树的所有条件在局部得到恢复,但可能导致下溢的传递。
-
双黑修正(BB-3)
节点s为红色。
解决方法:
从B-树的角度:令关键码s与p互换颜色。
从红黑树角度:以节点p为轴做一次旋转,并交换节点s与p的颜色。
存在的问题:双黑缺陷依然存在(子树 r 的黑高度并未复原),且缺陷位置的高度也并未上升。
PS:顺便一提,双黑修正就是为了让节点被删后子树 r 黑高度不变,进而满足条件(4)
进一步解决:转为(BB-1)或(BB-2-R)
-
双黑修正的复杂度
双黑修正过程总共耗时不超过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 天,点击查看活动详情”