红黑树,顾名思义,就是把平衡二叉搜索树的节点赋予两种颜色,经过定义几条规则,达到约束的目的。红黑树能够保证,每次插入删除操做后的重平衡,全树拓扑结构的改变仅须要常数个节点,最坏状况下须要对logn个节点重染色,可是就分摊意义仍然为O(1)。函数
须要知足的条件:spa
(1)树根始终为黑色3d
(2)外部节点均为黑色code
(3)红节点的孩子必然为黑色blog
(4)任一外部节点到根节点的路径,黑节点数量相同继承
从红黑树的规则,也能够获得一些结论,好比:红节点的父亲必然是黑节点;任一路径黑节点必然很多于红节点。递归
根据第(4)条规则,能够定义,从根节点通往任一节点的,除去根节点自己,沿途所通过黑节点的总数称为节点的黑深度(black depth)。因此也能够理解为,全部外部节点的黑深度一致。与树高相似,从任一节点通往其任一后代的沿途,通过的黑节点总数称为该节点的黑高度(black height)。rem
从黑高度的定义能够天然想到,红节点依赖于黑节点而存在,它们的存在甚至都不影响深度,是一种伴随的状态。事实上,红黑树能够认为是一种特殊的B-树,即(2,4)-树。由于红节点的父亲必然为黑节点,故能够把黑节点和他的红节点孩子看做一个B-树节点的关键码,这样就与(2,4)-树等效,其中黑节点必然位于中间,红关键码不超过两个。class
根据红黑树的定义,红黑树的高度范围为[log2(n+1),2*log2(n+1)],任一节点的左右高度(不是黑高度)相差不超过两倍。date
红黑树的实现
红黑树能够继承二叉搜索树,只须要重写插入和删除操做便可。同时,还须要从新定义高度,此时高度仅指黑高度。通常地外部节点(即便不存在)都认为是黑节点。
插入
根据红黑树的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来定义的,也就是说历来没有考虑被删除节点原位置的红黑关系,而是直接考虑交换后要删除节点的父亲、兄弟以及后继的红黑关系...这样精简了操做和代码,只须要分为四种状况就能够了,不过理解起来就变得复杂了。