红黑树-详细剖析-如果你有一天或者两天的时间,借助此文,能够学会红黑树的。
2012-11-29 21:11
423 查看
看了算法导论,也看了wikipedia,纠结了好几天,终于能看懂红黑树了。于是就写下这边博客,以供自己以后参考,或者能够和大家交流,希望能够共同学习。红黑树是很复杂的一种树。它的典型用途就是关联数组。
至于红黑树的删除,必定会给出耳目一新的思路。
一,红黑树的插入
按照常理,给出红黑树的性质:
1,每个节点或者是红色的或者是黑色的。
2,根节点是黑色的。
3,每个NIL节点都是黑色的。
4,如果一个节点是红色的,那么它的两个子节点都是黑色的。
5,对于每个节点,从该节点,到其子孙节点的所有路径上都包含相同数目的黑节点。
对于性质5,做一下解释,性质5所叙述的有些模糊,我觉得可以这样说:对于每个节点,从此节点开始,包括此节点,到其所有叶子节点的路径上的黑节点的个数相同,不应该说是子孙节点。还有要谨记第4条,这条的额外之意就是不能有两个连续节点的颜色为红色。
接下来解析旋转,即左旋或者右旋。先加以解释,然后在再给出伪代码和图。可以这样理解,既然决定进行旋转,那么旋转点x的左子节点或者右子节点就必然会存在,要不然旋转就会失去意义。旋转的实质是什么:对于左旋,就是旋点x下降到x的右孩子的位置,而x的左孩子上升到x之前的位置,然后把x的左孩子的右孩子连接到x的左孩子,x成为x之前左孩子的右孩子。如果觉得绕的话可以画下图。对于右旋与左旋相反。总之就记住两个关键字:下降和上升。加上图,我相信大家能够很好的理解旋转的意思。至于为什么会应用左旋或者右旋,那都是由于性质4,5导致的。那么可能会有疑问,通过旋转就可以使改变后的红黑树达到原来的状态吗?不是的,还要重新对相关的节点着色。到这里只需要知道,旋转是为了使红黑树保持上述的5个性质。接下来的问题就是,为什么红黑树这么特殊,要求有这么多。答案就是,经过硬性的规定这5条性质,可以达到这个要求:从根到叶子节点的最常的路径不多余最短路径的两倍。因此比如插入或者删除操作和查找某个值在最坏的情况下,也就是在经过最长路径的情况下,时间都与红黑树的高度成正比。至于非要追究为什么要有这5条性质,我想应该是红黑树的创建者们经常长时间的探索总结出来的。
左旋:
右旋:
接下来将给出红黑树插入的伪代码和给出红黑树调整的伪代码,然后给出八个实例图,通过这8个实例图的分析,我想大家能够熟悉红黑树插入过程中遇到的6种情况。
以下称6-11为case1,14-22为case2,20-22为case3,27-33为case4,37-44为case5,42-44为case6。如果有看过算法导论的,就会觉得和算法导论有出入,分别是case2和case5,为什么呢,因为case2和case3有代码重叠的部分。case2和case5都是双旋。等下在下面的例子中大家就会看到。
插入:
旋转:
实例一:注意节点7是黑色的,大家画图的时候要画成黑色的,我就不修改了。
从图中可以看到新插入的节点4和其父节点5,都是红色的违反了红黑树的性质4,同时注意到4的父亲节点的兄弟8是红色的,所以图中的条件满足case1,那么解决的办法是把x的父亲节点和它的叔叔节点都变成黑色的,然后把他们的祖父的颜色变成红色的。改变x的指向,指向其祖父节点。到下图:
这是调整后的状态,x指向7,条件符合case2,先把x重新指向,继而需要进行双旋,即先左旋,后右旋,下面给出这两部的图,注意,左旋后,要改变颜色的。
这就是插入节点4后,红黑树重新调整的过程,共经过了case1和case2,。
实例二:这次只给出图形,然后由大家自己画图分析,看是哪种情况。
实例三:
这种情况很容易看出就是case1,然后进行处理,得到如下的图:
分析一下后得知,当x指向7的时候,没有通过case2的测试,所以这个状态属于case3,对7的父节点重新着色为黑,祖父节点着色为红,然后以祖父节点为旋点,进行右旋后,红黑树就会转为如下的平衡状态:
实例四:同样只给出例图,然后由大家自己分析:
实例五:
同样可以分析出这张图的状态属于case1,然后进行调整后得到如下的图:
那么,大家看一下,上面这张图应该属于第几种情况呢,应该属于第5中情况了,所以需要进行双旋,旋点为14,先进行右旋:
右旋之后入上图,此时的x应该指向14,然后继续执行程序,将x的父节点着色成黑,祖父节点着色成红,然后以祖父节点为旋点进行左旋:
实例六:给出图形,大家尝试分析
实例七:
实例八:
经过这八个实例的分析,结合伪代码,我想肯定能够对红黑树的插入有少许的理解,最重要的是动手画图,在画图过程中就会逐步理解红黑树。
二,红黑树的删除
红黑树属于二叉查找树的一种,因此红黑树的删除类似于二叉查找树的删除。同时,又由于红黑树的特殊性质,删除节点之后要进行颜色的调整,所以情况就变得复杂了。建议在学习红黑树的删除时先看懂二叉查找树的删除情况。
先给出红黑树的删除的伪代码:
删除中应该考虑三种情况,如上述算法的1-3行所示:所删除节点的左孩子节点为空,所删除节点的右孩子为空,所删除节点的两个孩子节点都不为空。前两种情况的删除可以通过在z节点的父节点和z节点的不空的子节点之间建立一条链来删除z。第三种情况,如果z的左右子节点都不为空,则删除z的直接后继y,然后用y的内容来代替z的内容,其实直接钱驱也可以,看自己怎么选择。这个方法在数据结构书上应该看到过。着重讨论删除节点后红黑树的颜色的调整。如果所删除的节点的颜色是红色的,那么对红黑树的性质没有影响,为什么呢,理由有如下三点:红黑树中各节点的黑高度没有变化,也就是说,红黑树的第五个性质:对于每个节点,从此节点开始,到其所有叶子节点所经过的路径上的黑节点个数没有变化,是相同的。第二,不存在相邻的红节点。根据性质4:如果一个节点是红色的,那么它的两个子节点都是黑色的。这个红色节点的父节点必定是黑色的,如果删除这个红色的节点。那么是两个黑节点相连,所以没有连续的两个红色节点。第三,因为删除的节点是红色的,所以它就不可能是根节点,如果是根节点,那么红黑树在删除节点之前性质就是没有按照5个性质的约定,是错误的。
下面讨论,如果删除的节点是黑色的节点,给出红黑树颜色调整的例子:
1,如果删除的节点是根节点。分两种情况,如果根节点的左右子树中其中一个为空,想一下,红黑树的性质,最长的路径不超过最短路径的两倍。一个为空了,那么另一个子树的深度不会超过2,那么直接让它的左孩子或者右孩子代替它,不管怎么样,让新的根的颜色为黑色就好了。这样整个树种剩下的节点个数不超过2个了。另一种情况就是左右子树都不为空,那么就应该慎重了,再一下的分析中会给出答案。
2,第二种情况就是如果删除的这个节点的左右子树都不为空,那么就找到这个节点的直接后继,就是右子树中最左边的节点,称这个节点为x。那么x肯定没有左孩子,为什么呢,因为如果它有左孩子,它就不是最左边的节点。我们要讨论的是它有没有右孩子,如果没有,直接删除这个节点就好了,颜色什么的都不用调整。原因是删除它,虽然是黑色的节点,但是仍旧没有违反红黑树的性质。第一,没有违反性质2,没有违反性质4,那性质5呢,更没有。仔细分析一下性质5,对于每个节点,从此节点开始,到其叶子节点的路径上的黑节点个数都一样,注意,要有路径的。如果x连有孩子都没有,删除它,从此x父节点的左子树就为空了,没有了路径,怎么会有红黑节点呢。另外一种情况就是x有右子树。
3,着重讨论一般情况下的删除,删除调整树节点的颜色一共有8中情况,但是其中的四种和另外四种是对称的,所以理解了前四种,后四种就可以清楚了。读下伪代码就可以知道,这两组4个的情况是根据调整节点x是其父节点的左孩子还是右孩子来区分的。
先给出图,然后我们做一些假设,并依据这些假设来一步一步的调整红黑树的颜色。
我们假设A为删除节点的子节点,那么删除后及诶单调整后A的父亲为W,同时假设a,b,c,d,m,n为6棵不同的子树。根绝红黑树的性质,在不删除A的父节点之前,这个子树的叶子节点,到根节点W的所有路径中的黑节点个数是一样的。从图中可以看出,因为删除A的父节点是黑色的,所以a,b中含有的黑节点个数要比c,d,m,n中的黑节点个数少一个,故做如下的假设。
继续分析上图,可以看到,A的兄弟节点B,是红色的,注意,在这种情况下A的父节点的颜色必须是黑色的。因为红黑树中,不准许出现两个连续红色的节点。同时因为B是红色的,它的两个子节点C,D都是黑色的。那么删除A的父节点,碰到这种情况应该怎么做呢:解决办法,如伪代码所示意的,将A的兄弟B的颜色调整为黑色,A的父亲节点的颜色调整为红色。然后以W为旋点,进行左旋,得到如下的图:
此时在统计一下,a,b,c,d,m,n中叶子节点到根节点B的黑节点个数
可以看到,这次调整的目的没有达到。红黑树的性质5还是没有被满足的。那么接下来该怎么办呢。就是讨论A节点的兄弟节点C的孩子节点的颜色。给出如下图:
这是第一种情况,就是C节点的左右子节点的颜色都是黑色的。
重新调整后,出现了8棵小子树,就当从c中拿出其根节点,d中拿出其根节点F,统计一下,这个8个子树中最低端叶子节点到B的黑节点的个数。
在上图中A的兄弟节点C是黑色的,其左右子节点E,F都是黑色的,这种情况怎么办呢。让A的兄弟节点C的颜色变为红色,这样以后就成了如下图的情况:
图中出现了连续两个红节点的情况,解决的办法是什么呢:就是按照伪代码中所示的,把x节点重新指向A的父亲W,然后呢,根据while循环的条件可以得出,循环终止,将当前x的颜色着色为黑色。便有如下图:
然后再做一下统计:
这样红黑树就平衡了。
再分析下一种情况,就是A的兄弟节点C的右孩子是黑色,而左孩子是红色的情况。这种情况涉及双旋。
各个子树到B的黑节点个数为:
这种情况的解决办法是交换C节点E节点的颜色,然后以C节点为旋点进行右旋,得到如下的图(此时x节点指向A,w节点指向C)。
旋转后,统计一下各个子树到根节点B的黑节点个数,看此时的红黑树是不是平衡的。
可以看出,此时的红黑树是不平衡的,那么应该怎么解决呢。根据算法的伪代码,我们在这次的调整过程中,已经让w重新指向E。接下来对上图的做法就是,将A节点父节点的颜色转到w节点,然后将w节点即E节点的右孩子变成黑色的。w节点的父节点变成黑色的,然后以w节点E为旋点进行左旋。得到如下的图形:
然后我们再统计一下,各个子树到根节点B的黑节点个数:
一样多了,那么说明我们经过调整,红黑树在删除节点之后重新平衡了。
其他情况大家可以自己分析。
三,红黑树的c代码
至于红黑树的删除,必定会给出耳目一新的思路。
一,红黑树的插入
按照常理,给出红黑树的性质:
1,每个节点或者是红色的或者是黑色的。
2,根节点是黑色的。
3,每个NIL节点都是黑色的。
4,如果一个节点是红色的,那么它的两个子节点都是黑色的。
5,对于每个节点,从该节点,到其子孙节点的所有路径上都包含相同数目的黑节点。
对于性质5,做一下解释,性质5所叙述的有些模糊,我觉得可以这样说:对于每个节点,从此节点开始,包括此节点,到其所有叶子节点的路径上的黑节点的个数相同,不应该说是子孙节点。还有要谨记第4条,这条的额外之意就是不能有两个连续节点的颜色为红色。
接下来解析旋转,即左旋或者右旋。先加以解释,然后在再给出伪代码和图。可以这样理解,既然决定进行旋转,那么旋转点x的左子节点或者右子节点就必然会存在,要不然旋转就会失去意义。旋转的实质是什么:对于左旋,就是旋点x下降到x的右孩子的位置,而x的左孩子上升到x之前的位置,然后把x的左孩子的右孩子连接到x的左孩子,x成为x之前左孩子的右孩子。如果觉得绕的话可以画下图。对于右旋与左旋相反。总之就记住两个关键字:下降和上升。加上图,我相信大家能够很好的理解旋转的意思。至于为什么会应用左旋或者右旋,那都是由于性质4,5导致的。那么可能会有疑问,通过旋转就可以使改变后的红黑树达到原来的状态吗?不是的,还要重新对相关的节点着色。到这里只需要知道,旋转是为了使红黑树保持上述的5个性质。接下来的问题就是,为什么红黑树这么特殊,要求有这么多。答案就是,经过硬性的规定这5条性质,可以达到这个要求:从根到叶子节点的最常的路径不多余最短路径的两倍。因此比如插入或者删除操作和查找某个值在最坏的情况下,也就是在经过最长路径的情况下,时间都与红黑树的高度成正比。至于非要追究为什么要有这5条性质,我想应该是红黑树的创建者们经常长时间的探索总结出来的。
左旋:
LEFT-ROTATE(T, x) 1 y ← right[x] ▹ Set y. 2 right[x] ← left[y] ▹ Turn y's left subtree into x's right subtree. 3 p[left[y]] ← x 4 p[y] ← p[x] ▹ Link x's parent to y. 5 if p[x] = nil[T] 6 then root[T] ← y 7 else if x = left[p[x]] 8 then left[p[x]] ← y 9 else right[p[x]] ← y 10 left[y] ← x ▹ Put x on y's left. 11 p[x] ← y
右旋:
RB-INSERT(T, z) 1 y ← nil[T] 2 x ← root[T] 3 while x ≠ nil[T] 4 do y ← x 5 if key[z] < key[x] 6 then x ← left[x] 7 else x ← right[x] 8 p[z] ← y 9 if y = nil[T] 10 then root[T] ← z 11 else if key[z] < key[y] 12 then left[y] ← z 13 else right[y] ← z 14 left[z] ← nil[T] 15 right[z] ← nil[T] 16 color[z] ← RED 17 RB-INSERT-FIXUP(T, z)
接下来将给出红黑树插入的伪代码和给出红黑树调整的伪代码,然后给出八个实例图,通过这8个实例图的分析,我想大家能够熟悉红黑树插入过程中遇到的6种情况。
以下称6-11为case1,14-22为case2,20-22为case3,27-33为case4,37-44为case5,42-44为case6。如果有看过算法导论的,就会觉得和算法导论有出入,分别是case2和case5,为什么呢,因为case2和case3有代码重叠的部分。case2和case5都是双旋。等下在下面的例子中大家就会看到。
插入:
RB-INSERT(T, z) 1 y ← nil[T] 2 x ← root[T] 3 while x ≠ nil[T] 4 do y ← x 5 if key[z] < key[x] 6 then x ← left[x] 7 else x ← right[x] 8 p[z] ← y 9 if y = nil[T] 10 then root[T] ← z 11 else if key[z] < key[y] 12 then left[y] ← z 13 else right[y] ← z 14 left[z] ← nil[T] 15 right[z] ← nil[T] 16 color[z] ← RED 17 RB-INSERT-FIXUP(T, z)
旋转:
1while color[p[z]] == RED 2{ 3 if( p[z] == left[p[p[z]]]) 4 { 5 y ← right[p[p[z]]] 6 if color[y] == RED 7 { 8 color[p[z]] ← BLACK 9 color[y] ← BLACK 10 color[p[p[z]]] ← RED 11 z ← p[p[z]] 12 } 13 else 14 { 15 if (z == right[p[z]]) 16 { 17 z ← p[z] 18 LEFT-ROTATE(T, z) 19 } 20 color[p[z]] ← BLACK 21 color[p[p[z]]] ← RED 22 RIGHT-ROTATE(T, p[p[z]]) 23 } 24 } 25 else 26 { 27 y ← left[p[p[z]]] 28 if color[y] == RED 29 { 30 color[p[z]] ← BLACK 31 color[y] ← BLACK 32 color[p[p[z]]] ← RED 33 z ← p[p[z]] 34 } 35 else 36 { 37 if (z == left[p[z]]) 38 { 39 z ← p[z] 40 RIGHT-ROTATE(T, z) 41 } 42 color[p[z]] ← BLACK 43 color[p[p[z]]] ← RED 44 LEFT-ROTATE(T, p[p[z]]) 45 } 46 } 47 } 48 color[root[T]] ← BLACK
实例一:注意节点7是黑色的,大家画图的时候要画成黑色的,我就不修改了。
从图中可以看到新插入的节点4和其父节点5,都是红色的违反了红黑树的性质4,同时注意到4的父亲节点的兄弟8是红色的,所以图中的条件满足case1,那么解决的办法是把x的父亲节点和它的叔叔节点都变成黑色的,然后把他们的祖父的颜色变成红色的。改变x的指向,指向其祖父节点。到下图:
这是调整后的状态,x指向7,条件符合case2,先把x重新指向,继而需要进行双旋,即先左旋,后右旋,下面给出这两部的图,注意,左旋后,要改变颜色的。
这就是插入节点4后,红黑树重新调整的过程,共经过了case1和case2,。
实例二:这次只给出图形,然后由大家自己画图分析,看是哪种情况。
实例三:
这种情况很容易看出就是case1,然后进行处理,得到如下的图:
分析一下后得知,当x指向7的时候,没有通过case2的测试,所以这个状态属于case3,对7的父节点重新着色为黑,祖父节点着色为红,然后以祖父节点为旋点,进行右旋后,红黑树就会转为如下的平衡状态:
实例四:同样只给出例图,然后由大家自己分析:
实例五:
同样可以分析出这张图的状态属于case1,然后进行调整后得到如下的图:
那么,大家看一下,上面这张图应该属于第几种情况呢,应该属于第5中情况了,所以需要进行双旋,旋点为14,先进行右旋:
右旋之后入上图,此时的x应该指向14,然后继续执行程序,将x的父节点着色成黑,祖父节点着色成红,然后以祖父节点为旋点进行左旋:
实例六:给出图形,大家尝试分析
实例七:
实例八:
经过这八个实例的分析,结合伪代码,我想肯定能够对红黑树的插入有少许的理解,最重要的是动手画图,在画图过程中就会逐步理解红黑树。
二,红黑树的删除
红黑树属于二叉查找树的一种,因此红黑树的删除类似于二叉查找树的删除。同时,又由于红黑树的特殊性质,删除节点之后要进行颜色的调整,所以情况就变得复杂了。建议在学习红黑树的删除时先看懂二叉查找树的删除情况。
先给出红黑树的删除的伪代码:
1 if left[z] = nil[T] or right[z] = nil[T] 2 then y ← z 3 else y ← TREE-SUCCESSOR(z) 4 if left[y] ≠ nil[T] 5 then x ← left[y] 6 else x ← right[y] 7 p[x] ← p[y] 8 if p[y] = nil[T] 9 then root[T] ← x 10 else if y = left[p[y]] 11 then left[p[y]] ← x 12 else right[p[y]] ← x 13 if y ≠ z 14 then key[z] ← key[y] 15 copy y's satellite data into z 16 if color[y] = BLACK 17 then RB-DELETE-FIXUP(T, x) 18 return y可以清晰的看出红黑树的删除和二叉查找树的删除除了16,17行不相同之外,其他基本一致。(伪代码来自算法导论)。
删除中应该考虑三种情况,如上述算法的1-3行所示:所删除节点的左孩子节点为空,所删除节点的右孩子为空,所删除节点的两个孩子节点都不为空。前两种情况的删除可以通过在z节点的父节点和z节点的不空的子节点之间建立一条链来删除z。第三种情况,如果z的左右子节点都不为空,则删除z的直接后继y,然后用y的内容来代替z的内容,其实直接钱驱也可以,看自己怎么选择。这个方法在数据结构书上应该看到过。着重讨论删除节点后红黑树的颜色的调整。如果所删除的节点的颜色是红色的,那么对红黑树的性质没有影响,为什么呢,理由有如下三点:红黑树中各节点的黑高度没有变化,也就是说,红黑树的第五个性质:对于每个节点,从此节点开始,到其所有叶子节点所经过的路径上的黑节点个数没有变化,是相同的。第二,不存在相邻的红节点。根据性质4:如果一个节点是红色的,那么它的两个子节点都是黑色的。这个红色节点的父节点必定是黑色的,如果删除这个红色的节点。那么是两个黑节点相连,所以没有连续的两个红色节点。第三,因为删除的节点是红色的,所以它就不可能是根节点,如果是根节点,那么红黑树在删除节点之前性质就是没有按照5个性质的约定,是错误的。
下面讨论,如果删除的节点是黑色的节点,给出红黑树颜色调整的例子:
1 while x ≠ root[T] and color[x] = BLACK 2 do if x = left[p[x]] 3 then w ← right[p[x]] 4 if color[w] = RED 5 then color[w] ← BLACK ▹ Case 1 6 color[p[x]] ← RED ▹ Case 1 7 LEFT-ROTATE(T, p[x]) ▹ Case 1 8 w ← right[p[x]] ▹ Case 1 9 if color[left[w]] = BLACK and color[right[w]] = BLACK 10 then color[w] ← RED ▹ Case 2 11 x p[x] ▹ Case 2 12 else if color[right[w]] = BLACK 13 then color[left[w]] ← BLACK ▹ Case 3 14 color[w] ← RED ▹ Case 3 15 RIGHT-ROTATE(T, w) ▹ Case 3 16 w ← right[p[x]] ▹ Case 3 17 color[w] ← color[p[x]] ▹ Case 4 18 color[p[x]] ← BLACK ▹ Case 4 19 color[right[w]] ← BLACK ▹ Case 4 20 LEFT-ROTATE(T, p[x]) ▹ Case 4 21 x ← root[T] ▹ Case 4 22 else (same as then clause with "right" and "left" exchanged) 23 color[x] ← BLACK
1,如果删除的节点是根节点。分两种情况,如果根节点的左右子树中其中一个为空,想一下,红黑树的性质,最长的路径不超过最短路径的两倍。一个为空了,那么另一个子树的深度不会超过2,那么直接让它的左孩子或者右孩子代替它,不管怎么样,让新的根的颜色为黑色就好了。这样整个树种剩下的节点个数不超过2个了。另一种情况就是左右子树都不为空,那么就应该慎重了,再一下的分析中会给出答案。
2,第二种情况就是如果删除的这个节点的左右子树都不为空,那么就找到这个节点的直接后继,就是右子树中最左边的节点,称这个节点为x。那么x肯定没有左孩子,为什么呢,因为如果它有左孩子,它就不是最左边的节点。我们要讨论的是它有没有右孩子,如果没有,直接删除这个节点就好了,颜色什么的都不用调整。原因是删除它,虽然是黑色的节点,但是仍旧没有违反红黑树的性质。第一,没有违反性质2,没有违反性质4,那性质5呢,更没有。仔细分析一下性质5,对于每个节点,从此节点开始,到其叶子节点的路径上的黑节点个数都一样,注意,要有路径的。如果x连有孩子都没有,删除它,从此x父节点的左子树就为空了,没有了路径,怎么会有红黑节点呢。另外一种情况就是x有右子树。
3,着重讨论一般情况下的删除,删除调整树节点的颜色一共有8中情况,但是其中的四种和另外四种是对称的,所以理解了前四种,后四种就可以清楚了。读下伪代码就可以知道,这两组4个的情况是根据调整节点x是其父节点的左孩子还是右孩子来区分的。
先给出图,然后我们做一些假设,并依据这些假设来一步一步的调整红黑树的颜色。
我们假设A为删除节点的子节点,那么删除后及诶单调整后A的父亲为W,同时假设a,b,c,d,m,n为6棵不同的子树。根绝红黑树的性质,在不删除A的父节点之前,这个子树的叶子节点,到根节点W的所有路径中的黑节点个数是一样的。从图中可以看出,因为删除A的父节点是黑色的,所以a,b中含有的黑节点个数要比c,d,m,n中的黑节点个数少一个,故做如下的假设。
继续分析上图,可以看到,A的兄弟节点B,是红色的,注意,在这种情况下A的父节点的颜色必须是黑色的。因为红黑树中,不准许出现两个连续红色的节点。同时因为B是红色的,它的两个子节点C,D都是黑色的。那么删除A的父节点,碰到这种情况应该怎么做呢:解决办法,如伪代码所示意的,将A的兄弟B的颜色调整为黑色,A的父亲节点的颜色调整为红色。然后以W为旋点,进行左旋,得到如下的图:
此时在统计一下,a,b,c,d,m,n中叶子节点到根节点B的黑节点个数
可以看到,这次调整的目的没有达到。红黑树的性质5还是没有被满足的。那么接下来该怎么办呢。就是讨论A节点的兄弟节点C的孩子节点的颜色。给出如下图:
这是第一种情况,就是C节点的左右子节点的颜色都是黑色的。
重新调整后,出现了8棵小子树,就当从c中拿出其根节点,d中拿出其根节点F,统计一下,这个8个子树中最低端叶子节点到B的黑节点的个数。
在上图中A的兄弟节点C是黑色的,其左右子节点E,F都是黑色的,这种情况怎么办呢。让A的兄弟节点C的颜色变为红色,这样以后就成了如下图的情况:
图中出现了连续两个红节点的情况,解决的办法是什么呢:就是按照伪代码中所示的,把x节点重新指向A的父亲W,然后呢,根据while循环的条件可以得出,循环终止,将当前x的颜色着色为黑色。便有如下图:
然后再做一下统计:
这样红黑树就平衡了。
再分析下一种情况,就是A的兄弟节点C的右孩子是黑色,而左孩子是红色的情况。这种情况涉及双旋。
各个子树到B的黑节点个数为:
这种情况的解决办法是交换C节点E节点的颜色,然后以C节点为旋点进行右旋,得到如下的图(此时x节点指向A,w节点指向C)。
旋转后,统计一下各个子树到根节点B的黑节点个数,看此时的红黑树是不是平衡的。
可以看出,此时的红黑树是不平衡的,那么应该怎么解决呢。根据算法的伪代码,我们在这次的调整过程中,已经让w重新指向E。接下来对上图的做法就是,将A节点父节点的颜色转到w节点,然后将w节点即E节点的右孩子变成黑色的。w节点的父节点变成黑色的,然后以w节点E为旋点进行左旋。得到如下的图形:
然后我们再统计一下,各个子树到根节点B的黑节点个数:
一样多了,那么说明我们经过调整,红黑树在删除节点之后重新平衡了。
其他情况大家可以自己分析。
三,红黑树的c代码
#include<stdio.h> #include<stdlib.h> #define BLACK 0 #define RED 1 typedef struct rb_tree_node{ int key; struct rb_tree_node *parent; struct rb_tree_node *left; struct rb_tree_node *right; int color; }RBTNode, *RBTree; void left_rotate(RBTree *T, RBTNode *cur_node) { RBTNode *rchild_node; // rigth child node of current node rchild_node = cur_node->right; cur_node->right = rchild_node->left; if(cur_node->right != NULL){ (cur_node->right)->parent = cur_node; } rchild_node->parent = cur_node->parent; if(cur_node->parent == NULL){ *T = rchild_node; } else { if(cur_node == (cur_node->parent)->left) (cur_node->parent)->left = rchild_node; else (cur_node->parent)->right = rchild_node; } cur_node->parent = rchild_node; rchild_node->left = cur_node; // cur_node to be left child } void right_rotate(RBTree *T, RBTNode *cur_node) { RBTNode *lchild_node; lchild_node = cur_node->left; cur_node->left = lchild_node->right; if(cur_node->left != NULL) { (cur_node->left)->parent = cur_node; } lchild_node->parent = cur_node->parent; if(cur_node->parent == NULL) { *T = lchild_node; } else { if(cur_node == (cur_node->parent)->left) (cur_node->parent)->left = lchild_node; else (cur_node->parent)->right = lchild_node; } cur_node->parent = lchild_node; lchild_node->right = cur_node; // cur_node to be left child of lchild_node } void rb_tree_insert_fixup(RBTree *T, RBTNode *cur_node) { RBTNode *parent; RBTNode *grandparent; RBTNode *uncle; while((parent = cur_node->parent) != NULL && parent->color == RED){ grandparent = parent->parent; if(grandparent == NULL) break; if(parent == grandparent->left) { // brother of node's parent uncle = grandparent->right; if(uncle != NULL && uncle->color == RED) {//case 1 uncle->color = BLACK; parent->color = BLACK; grandparent->color = RED; cur_node = grandparent; } else {//case 2,3 if(cur_node == parent->right) { cur_node = parent; left_rotate(T, cur_node); } //case 3 parent = cur_node->parent; parent->color = BLACK; grandparent->color = RED; right_rotate(T, grandparent); } } else { uncle = grandparent->left; if(uncle != NULL && uncle->color == RED) {//case 4 uncle->color = BLACK; parent->color = BLACK; grandparent->color =RED; cur_node = grandparent; } else {//case 5 ,6 if(cur_node == parent->left) { cur_node = parent; right_rotate(T, cur_node); } //case 6 parent = cur_node->parent; parent->color = BLACK; grandparent->color = RED; left_rotate(T, grandparent); } } } (*T)->color = BLACK; } int rb_tree_search_auxiliary(RBTree T, RBTNode *parent, RBTNode **cur_node, int key) { if(!T){ *cur_node = parent; return 0; } if(key == T->key) { *cur_node = T; return 1; } else if(key < T->key){ return rb_tree_search_auxiliary(T->left, T, cur_node, key); } else { return rb_tree_search_auxiliary(T->right, T, cur_node, key); } } void rb_tree_insert(RBTree *T, int key) { RBTNode *p; if(!rb_tree_search_auxiliary(*T, NULL, &p, key)) { RBTNode *s = (RBTNode*)malloc(sizeof(RBTNode)); s->key = key; s->left = NULL; s->right = NULL; s->parent = NULL; s->color = RED; if(!(*T)) { (*T) = s; } else { if(key < p->key) { p->left = s; } else { p->right = s; } s->parent = p; } rb_tree_insert_fixup(T, s); } } int rb_tree_search(RBTree T, int key) { RBTNode *p; int result = 0; result = rb_tree_search_auxiliary(T, NULL, &p, key); return result; free(p); } void rb_tree_delete_fixup(RBTree *T, RBTNode *cur_node) { RBTNode *parent = NULL; RBTNode *uncle = NULL; while((cur_node != NULL) && (cur_node != *T) && (cur_node->color == BLACK)) { parent = cur_node->parent; if(cur_node == parent->left) { uncle = parent->right; if(uncle->color == RED) { uncle->color = BLACK; parent->color = RED; left_rotate(T, parent); uncle = cur_node->parent->right; } if(uncle->left->color == BLACK && uncle->right->color == BLACK) { uncle->color = RED; cur_node = cur_node->parent; } else{ if(uncle->right->color == BLACK) { uncle->left->color = BLACK; uncle->color = RED; right_rotate(T, uncle); uncle = cur_node->right; } uncle->color = uncle->parent->color; uncle->right->color = BLACK; uncle->parent->color = BLACK; left_rotate(T, uncle->parent); cur_node = *T; } } else { uncle = parent->left; if(uncle->color == RED) { parent->color = RED; uncle->color = BLACK; right_rotate(T, parent); uncle = cur_node->parent->left; } if(uncle->left->color == BLACK && uncle->right->color == BLACK) { uncle->color = RED; cur_node = parent; } else{ if(uncle->left->color == BLACK) { uncle->right->color = BLACK; uncle->color = RED; left_rotate(T, uncle); uncle = parent->left; } uncle->color = parent->color; parent->color = BLACK; uncle->left->color = BLACK; right_rotate(T, uncle->parent); cur_node = *T; } } } if(cur_node != NULL) cur_node->color = BLACK; } RBTNode* rb_tree_delete_auxiliary(RBTree *T, RBTNode *old_node) { RBTNode* child_node = NULL;// 要删除节点的孩子节点 RBTNode* deleting_node = NULL; //要删除的节点 if(old_node->left == NULL || old_node->right == NULL){ //有一个为空,或者全为空 deleting_node = old_node; } else {//两个子节点都不为空,则 deleting_node = old_node->right; while(deleting_node->left != NULL) deleting_node = deleting_node->left; } if(deleting_node->left != NULL) child_node = deleting_node->left; else child_node = deleting_node->right; if(child_node != NULL) {//孩子节点全为空的情况 child_node->parent = deleting_node->parent; } if(child_node != NULL && child_node->parent == NULL){//说明删掉的是根节点 *T = child_node; } else { if(deleting_node == (deleting_node->parent)->left) (deleting_node->parent)->left = child_node; else (deleting_node->parent)->right = child_node; } if(deleting_node != old_node) { old_node->key = deleting_node->key; } if(deleting_node->color == BLACK){ rb_tree_delete_fixup(T, child_node); } return deleting_node; } int rb_tree_delete(RBTree *T, int key) { RBTNode *p; RBTNode *rst = NULL; int result = 0; result = rb_tree_search_auxiliary(*T, NULL, &p, key); if(!result){//要删除的目标节点不存在,删除失败 return 0; } else { rst = rb_tree_delete_auxiliary(T, p); if(rst){ free(rst); return 1; }else{ return 0; } } } void pre_order_traverse(RBTree T) { if(T) { printf("key=%d,color=%d\n", T->key, T->color); pre_order_traverse(T->left); pre_order_traverse(T->right); } } void main() { RBTree T = NULL; rb_tree_insert(&T, 41); rb_tree_insert(&T, 38); rb_tree_insert(&T, 31); rb_tree_insert(&T, 12); rb_tree_insert(&T, 19); rb_tree_insert(&T, 8); pre_order_traverse(T); if(rb_tree_search(T, 19)) printf("19 exist!\n"); if(rb_tree_delete(&T,38)) printf("delete 38 ok!\n"); printf("root=%d\n",T->key); pre_order_traverse(T); }
相关文章推荐
- 真的没有几个人能做到!--如果你休息一天,实力就会倒退两天--2500多个日子里只有5天在休息!
- 最近完成的模块或者项目的总结,如果有时间具体写写
- 添加时间数据时 出现多一天或者少一天的问题 解决方案
- 在线Word编辑的jQuery插件时间:2010-12-29 09:15点击:122 次 【大 中 小】 在做OA或者工作流程的网站中,常常能够看到一些在线Word编辑进行文档处理的功能,这里我开发了
- project设置工期为1个工作日,但开始时间与结束时间不是同一天,如何解决或者是设置?
- 如果时间能够倒流
- 如果每15分钟提交一次数据,则一天中应有96条数据。如有镂空,则补全没有提交的数据时间
- 在HTML中显示图片时希望如果图片不存在或者无法显示时,能够显示默认图片
- 检查一个路径下文件是否存在,如果不存在设置一个定时器,在定时器内每隔一定时间检查一次,直到该文件存在返回成功,或者定时超时返回失败
- swift 一天入门,两天学会
- ALTER TABLE 只允许添加满足下述条件的列: 列可以包含 Null 值;或者列具有指定的 DEFAULT 定义;或者要添加的列是标识列或时间戳列;或者,如果前几个条件均未满足,则表必须为空以允
- JS时间的计算,当前日期加一天或者几天的计算
- 将近一半的用户希望能够在两秒内或者更短的时间内打开网站
- Calendar获得当前时间之前或之后的一周或者一天或者其他任意天数的时间点
- [ArgumentException: 可能证书“CN=JRNet01-PC”没有能够进行密钥交换的私钥,或者进程可能没有访问私钥的权限。有关详细信息,请参见内部异常。]
- WINDOWS2003服务器如果需要perl或者java在服务器上能够执行可执行程序,需要做哪些配置?
- 如果给我一个时间节点,我想去到我离开这个世界的那一天
- C语言实现时间的加一天或者减一天
- js 获取某一天的前一天时间或者后一天时间
- 解决Mysql连接池被关闭 ,hibernate尝试连接不能连接的问题。 (默认mysql连接池可以访问的时间为8小时,如果超过8小时没有连接,mysql会自动关闭连接池。