8.3.2 红黑树接口定义
基于BST定义的红黑树接口:
0001 #include "BST/BST.h" //基于BST实现RedBlack
0002 template <typename T> class RedBlack : public BST<T> { //RedBlack树模板类
0003 protected:
0004 void solveDoubleRed ( BinNodePosi<T> x ); //双红修正
0005 void solveDoubleBlack ( BinNodePosi<T> x ); //双黑修正
0006 int updateHeight ( BinNodePosi<T> x ); //更新节点x的高度(重写)
0007 public:
0008 BinNodePosi<T> insert ( const T& e ); //插入(重写)
0009 bool remove ( const T& e ); //删除(重写)
0010 // BST::search()等其余接口可直接沿用
0011 };
基用以简化红黑树算法描述的宏:
0001 #define IsBlack(p) ( ! (p) || ( RB_BLACK == (p)->color ) ) //外部节点也视作黑节点
0002 #define IsRed(p) ( ! IsBlack(p) ) //非黑即红
0003 #define BlackHeightUpdated(x) ( /*RedBlack高度更新条件*/ \
0004 ( stature( (x).lc ) == stature( (x).rc ) ) && \
0005 ( (x).height == ( IsRed(& x) ? stature( (x).lc ) : stature( (x).lc ) + 1 ) ) \
0006 )
红黑树节点的黑高度更新:
0001 template <typename T> int RedBlack<T>::updateHeight ( BinNodePosi<T> x ) { //更新节点高度
0002 return x->height = IsBlack ( x ) + max ( stature ( x->lc ), stature ( x->rc ) ); //孩子黑高度通常相等,除非出现双黑
0003 }
8.3.3 节点插入算法
-
节点插入与双红现象
如代码8.16所示,不妨假定经调用接口search(e)做查找之后,确认目标节点尚不存在。于是,在查找终止的位置x处创建节点,并随即将其染成红色(除非此时全树仅含一个节点)。现在,对照红黑树的四项条件,唯有(3)未必满足——亦即,此时x的父亲也可能是红色。
分析:为什么插入的节点要染成红色?因为需满足条件(4)
代码8.16 红黑树insert()接口:
0001 template <typename T> BinNodePosi<T> RedBlack<T>::insert ( const T& e ) { //将e插入红黑树 0002 BinNodePosi<T> & x = search ( e ); if ( x ) return x; //确认目标不存在(留意对_hot的设置) 0003 x = new BinNode<T> ( e, _hot, NULL, NULL, 0 ); _size++; //创建红节点x:以_hot为父,黑高度0 0004 BinNodePosi<T> xOld = x; solveDoubleRed ( x ); return xOld; //经双红修正后,即可返回 0005 } //无论e是否存在于原树中,返回时总有x->data == e因新节点的引入,而导致父子节点同为红色的此类情况,称作“双红”(double red) 。 为修正双红缺陷,可调用solveDoubleRed(x)接口。每引入一个关键码,该接口都可能迭代地调用多次。在此过程中,当前节点x的兄弟及两个孩子(初始时都是外部节点),始终均为黑色。
将x的父亲与祖父分别记作p和g。既然此前的红黑树合法,故作为红节点p的父亲,g必然存在且为黑色。g作为内部节点,其另一孩子(即p的兄弟、x的叔父)也必然存在,将其记作u。以下,视节点u的颜色不同,分两类情况分别处置。
-
双红修正(RR-1)
u为黑色。此时x的兄弟、两个孩子的黑高度均与u相等。
可等价于B-树,黑高度相等则同在B-树一个节点内。
此时红黑树条件(3)的违反,从B-树角度等效地看,即同一节点不应包含紧邻的红色关键码。
(a、b为两种可能情况,且存在对称情况)
解决方法:令p与g互换颜色,对节点x、p和g及其四颗子树,做一次局部“3 + 4”重构。
最后:a - x b - p c - g
-
双红修正(RR-2)
u为红色。此时u的左、右孩子非空且均为黑色,其黑高度必与x的兄弟以及两个孩子相等。
可等价于B-树,黑高度相等则同在B-树一个节点内。
此时红黑树条件(3)的违反,从B-树角度等效地看,即该节点因超过4度而发生上溢。
(a、b为两种可能情况,且存在对称情况)
解决方法:红节点p和u转为黑色,黑节点g转为红色,x保持红色。从图(c')B-树的角度看,等效于上溢节点的一次分裂。
上溢的向上传播:子树根节点g转为红色之后,有可能在更高层次再次引发双红现象。
如何解决上溢传播:可以等效地将g视作新插入的节点,分以上两类情况如法处置。这样的迭代在红黑树中至多进行O(logn)次。
特别地,若最后一步迭代之后导致原树根的分裂,并由g独立地构成新的树根节点,则应遵照红黑树条件(1)的要求,强行将其转为黑色——如此,全树的黑高度随即增加一层。
-
双红修正的复杂度
节点插入之后的双红修正,累计耗时不会超过O(logn)。即便计入此前的关键码查找以及节点接入等操作,红黑树的每次节点插入操作,都可在O(logn)时间内完成。
需要特别指出的是,只有在RR-1修复时才需做1~2次旋转;而且一旦旋转后,修复过程必然随即完成。故就全树拓扑结构而言,每次插入后仅涉及常数次调整;而且稍后将会看到,红黑树的节点删除操作亦是如此——回顾7.4节的AVL树,却只能保证前一点。
-
双红修正算法的实现
双红修正solveDoubleRed():
0001 /****************************************************************************************** 0002 * RedBlack双红调整算法:解决节点x与其父均为红色的问题。分为两大类情况: 0003 * RR-1:2次颜色翻转,2次黑高度更新,1~2次旋转,不再递归 0004 * RR-2:3次颜色翻转,3次黑高度更新,0次旋转,需要递归 0005 ******************************************************************************************/ 0006 template <typename T> void RedBlack<T>::solveDoubleRed ( BinNodePosi<T> x ) { //x当前必为红 0007 if ( IsRoot ( *x ) ) //若已(递归)转至树根,则将其转黑,整树黑高度也随之递增 0008 { _root->color = RB_BLACK; _root->height++; return; } //否则,x的父亲p必存在 0009 BinNodePosi<T> p = x->parent; if ( IsBlack ( p ) ) return; //若p为黑,则可终止调整。否则 0010 BinNodePosi<T> g = p->parent; //既然p为红,则x的祖父必存在,且必为黑色 0011 BinNodePosi<T> u = uncle ( x ); //以下,视x叔父u的颜色分别处理 0012 if ( IsBlack ( u ) ) { //u为黑色(含NULL)时 0013 if ( IsLChild ( *x ) == IsLChild ( *p ) ) //若x与p同侧(即zIg-zIg或zAg-zAg),则 0014 p->color = RB_BLACK; //p由红转黑,x保持红 0015 else //若x与p异侧(即zIg-zAg或zAg-zIg),则 0016 x->color = RB_BLACK; //x由红转黑,p保持红 0017 g->color = RB_RED; //g必定由黑转红 0018 ///// 以上虽保证总共两次染色,但因增加了判断而得不偿失 0019 ///// 在旋转后将根置黑、孩子置红,虽需三次染色但效率更高 0020 BinNodePosi<T> gg = g->parent; //曾祖父(great-grand parent) 0021 BinNodePosi<T> r = FromParentTo ( *g ) = rotateAt ( x ); //调整后的子树根节点 0022 r->parent = gg; //与原曾祖父联接 0023 } else { //若u为红色 0024 p->color = RB_BLACK; p->height++; //p由红转黑 0025 u->color = RB_BLACK; u->height++; //u由红转黑 0026 g->color = RB_RED; //在B-树中g相当于上交给父节点的关键码,故暂标记为红 0027 solveDoubleRed ( g ); //继续调整:若已至树根,接下来的递归会将g转黑(尾递归,不难改为迭代) 0028 } 0029 }
“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 16 天,点击查看活动详情”