红黑树
Posted luStar
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了红黑树相关的知识,希望对你有一定的参考价值。
红黑树,顾名思义,就是把平衡二叉搜索树的节点赋予两种颜色,通过定义几条规则,达到约束的目的。红黑树可以保证,每次插入删除操作后的重平衡,全树拓扑结构的改变仅需要常数个节点,最坏情况下需要对logn个节点重染色,但是就分摊意义仍然为O(1)。
需要满足的条件:
(1)树根始终为黑色
(2)外部节点均为黑色
(3)红节点的孩子必然为黑色
(4)任一外部节点到根节点的路径,黑节点数量相同
从红黑树的规则,也可以得到一些结论,比如:红节点的父亲必然是黑节点;任一路径黑节点必然不少于红节点。
根据第(4)条规则,可以定义,从根节点通往任一节点的,除去根节点本身,沿途所经过黑节点的总数称为节点的黑深度(black depth)。所以也可以理解为,所有外部节点的黑深度一致。与树高类似,从任一节点通往其任一后代的沿途,经过的黑节点总数称为该节点的黑高度(black height)。
从黑高度的定义可以自然想到,红节点依赖于黑节点而存在,它们的存在甚至都不影响深度,是一种伴随的状态。事实上,红黑树可以认为是一种特殊的B-树,即(2,4)-树。因为红节点的父亲必然为黑节点,故可以把黑节点和他的红节点孩子看作一个B-树节点的关键码,这样就与(2,4)-树等效,其中黑节点必然位于中间,红关键码不超过两个。
根据红黑树的定义,红黑树的高度范围为[log2(n+1),2*log2(n+1)],任一节点的左右高度(不是黑高度)相差不超过两倍。
红黑树的实现
红黑树可以继承二叉搜索树,只需要重写插入和删除操作即可。同时,还需要重新定义高度,此时高度仅指黑高度。一般地外部节点(即使不存在)都认为是黑节点。
插入
根据红黑树的4个要求,可以自然想到,插入的节点不可能为黑,否则将彻底打破全树的平衡,如果父亲以及兄弟节点均为黑,那么将难以调整。反之,插入的节点统一为红色,这样仅会导致局部不满足规则(3)。因此,插入后产生这种问题,称为“双红”。
考虑插入后的几种情况:
(1)父亲节点为黑,这样不需要调整。
(2)父亲节点为红,父亲的兄弟节点为黑。这时即构成了一个3-4问题,对这几个节点进行旋转操作,并将父亲节点染色为黑,祖父染为红即可。这种情况只需要1-2次旋转,2次染色,调整即可完成。
(3)父亲节点为红,父亲的兄弟节点也为红。此时无法通过旋转操作,考虑类似B-树的上溢操作,即节点因为超过4度而上溢。从B-树的观点看,将祖父上溢一层,两侧的节点分裂为两个新节点,并将父亲节点染黑,父亲的兄弟节点也染黑即可。祖父上溢后,可能会导致上一层的双红,于是需要继续向上递归。如果递归至根节点,那么需要把g染黑,同时全树的高度+1。这种情况每次操作只需要三次染色,但可能最多需要O(logn)次迭代。
删除
红黑树的删除操作,与一般二叉搜索树相同,先找到要删除的元素,然后再删除即可。先进行查找以及替换工作,如果被删除的元素两个孩子不都存在,替换为存在的孩子;如果两个孩子都存在,与直接后继交换。x为实际被删除的节点,而r是它的接替者,r的兄弟为外部节点w=NULL(因为x是后继,x不可能有左孩子),这样可以将x统一视为双分支的节点,方便统一处理。(要注意,这里已经进行了替换,即x是实际删除的节点,后继需要按照二叉树的后继来寻找,并交换他们的数值。如果要删除的是叶节点,那么不存在后继,w本来即为NULL,不需要假设一个,直接释放掉这个节点。总之,实际处理的节点是原来节点的后继,不过已经交换了数值,r是该节点的后继,即原来节点位置后继的后继)
替换以及删除的代码如下:
1 template<typename T> static BinNodePosi(T) removeAt(BinNodePosi(T)& x, BinNodePosi(T)& hot) 2 { 3 BinNodePosi(T) w = x;//实际被摘除的节点 4 BinNodePosi(T) succ = NULL; 5 if (!(x->lc))//如果左孩子为空 6 succ = x = x->rc;//直接替换为右子树 7 else if (!(x->rc))//如果右孩子为空 8 succ = x = x->lc; 9 else//左右孩子都存在,选择x的直接后继作为实际摘除的节点(可以画个图来理解) 10 { 11 w = w->succ(); 12 swap(x->data, w->data);//交换x与其直接后继的数值 13 BinNodePosi(T) u = w->parent; 14 ((u == x) ? u->rc : u->lc) = succ = w->rc;//隔离节点w,把w的孩子与原树连接起来 15 } 16 hot = w->parent;//记录被删除节点的父亲 17 if (succ) succ->parent = hot;//将被删除节点的接替者与hot连接 18 release(w->data); release(w); //释放被删除的节点 19 return succ;//返回后继(接替) 20 } 21 template<typename T> bool RedBlack<T>::remove(const T& e) 22 { 23 BinNodePosi(T)& x = search(e); if (!x) return false; 24 BinNodePosi(T) r = removeAt(x, _hot); if (!(--_size)) return true;//删除后不存在元素直接返回 25 if (!_hot)//删除的节点为根节点 26 { 27 _root->color = RB_BLACK; updateHeight(_root); return true; 28 } 29 if (BalckHegihtUpdated(*_hot)) return true;//如果祖先黑深度依然平衡,即可返回 30 if (IsRed(r))//如果接替的节点为红,直接转为黑即可 31 { 32 r->color = RB_BLACK; r->height++; return true; 33 } 34 //否则,需要进行双黑调整 35 solveDoubleBlack(r); return true; 36 }
这样,如果实际被删除的节点x为红色,r为黑色,那么直接删除x,将r接入即可,可以等效视作抛弃w。如果x为黑色,r为红色,那么删除x后将r染黑接入即可。
不过删除的时候,可能造成双黑问题,即被删除的节点和接替的后继,都是黑色,这样会导致局部高度降低,从而不满足红黑树的条件。假设实际被删除节点为x,接替者为r,兄弟为s,兄弟的一个孩子为t,x的父亲为p,在这里把双黑分为四种情况:
(1)s有一个孩子t为红色,另外一个孩子不确定颜色,p的颜色也不确定。此时,可以考虑旋转操作(在B-树中为下溢操作),即s为轴的zig旋转,并且把p和t染黑,s继承p的颜色。这样,删除操作即宣告完成。
(2)s及其两个孩子均为黑色,p为红色。此时,删除x后,可以将p“拉下来”,合并为一个新节点,并且交换p和s的颜色。因为从B-树的意义,p为红色,那么p的兄弟必然有一个为黑色,故不可能发生新的下溢。
(3)s及其两个孩子均为黑色,p为黑色。这种情况,删除x后,同样将p下降一层,并把s染为红色。此时,子树整体黑高度下降,必然引发持续上层下溢,需要继续迭代。
(4)s为红色,p必为黑色。此时,删除x仅造成以x为根的子树高度下降,故从红黑树角度,以p为轴进行一次旋转,并交换s和p的颜色即可。此时会有新的问题,即r的高度并没有恢复。但是,r有了新的兄弟s\',而s\'必然是黑节点,此时问题转换为了(1)或者(2):如果s\'有红孩子,那么转换为(1),如果s\'没有红孩子,转换为(2)。因此,这种情况也不需要迭代,只需要4+1或者4+2。
实现代码完全照抄的0 0以后有时间一定自己写一下
1 template<typename T> void RedBlack<T>::solveDoubleBlack(BinNodePosi(T) r)//输入为用来替换x的后继 2 { 3 BinNodePosi(T) p = r ? r->parent : _hot; if (!p) return;//r的父亲,不存在可以直接退出 4 BinNodePosi(T) s = (p->lc == r) ? p->rc : p->lc;//s为r的兄弟 5 if (IsBlack(s))//s为黑 6 { 7 BinNodePosi(T) t = NULL;//s的红孩子(若左右均红,左优先) 8 if (IsRed(s->rc)) t = s->rc; 9 if (IsRed(s->lc)) t = s->lc; 10 if (t)//s为黑且有红孩子的时候(BB-1) 11 { 12 RBColor oldColor = p->color;//备份原子树根节点p的颜色 13 //进行旋转重平衡并将新子树的左右染黑 14 BinNodePosi(T) b = FromParentTo(*p) = rotateAt(t);//返回子树父亲节点 15 if (HasLChild(*b)) { b->lc->color = RB_BLACK; updateHeight(b->lc); } 16 if (HasRChild(*b)) { b->rc->color = RB_BLACK; updateHeight(b->rc); } 17 b->color = oldColor; updateHeight(b); 18 } 19 else//s为黑但是没有红孩子 20 { 21 s->color = RB_RED; s->height--;//s转红 22 if (IsRed(p))//BB-2R 23 p->color = RB_BLACK;//p转黑但是高度不变 24 else//BB-2B 25 { 26 p->height--;//p保持黑但是高度下降 27 solveDoubleBlack(p);//递归上溯 28 } 29 } 30 } 31 else//s为红,p以及s的两个孩子必然都黑 32 { 33 s->color = RB_BLACK; p->color = RB_RED;//s转黑p转红,交换 34 BinNodePosi(T) t = IsLChild(*s) ? s->lc; s->rc;//取t与父亲s同侧 35 _hot = p; FromParentTo(*p) = rotateAt(t);//p原父亲的孩子为现在的根节点 36 solveDoubleBlack(r);//p为红,后续只能为BB-1或BB-2R 37 } 38 }
有问题可以参考邓俊辉大大的原书,虽然我觉得我解释地比原书好一些了,我看原书看了半天才看明白...主要是在二叉搜索树中的removeAt()函数,这个函数会执行一个比较聪明的对策:不真的删除节点后再连接节点,而是交换内部数值后,删除那个交换后的节点,这个实际被删除的节点是原节点的后继,再把子树接入被删除节点的父亲位置_hot。而在这里,x就是这个后继,而r是x的后继,可以说是后继的后继。一切p s r t都是从x来定义的,也就是说从来没有考虑被删除节点原位置的红黑关系,而是直接考虑交换后要删除节点的父亲、兄弟以及后继的红黑关系...这样精简了操作和代码,只需要分为四种情况就可以了,不过理解起来就变得复杂了。
以上是关于红黑树的主要内容,如果未能解决你的问题,请参考以下文章