算法导论学习-RED-BLACK TREE

1. 红黑树(RED-BLACK TREE)引言:

-------------------------------------

红黑树(RBT)可以说是binary-search tree的非严格的平衡版本。与之相应的是平衡二叉树(Balanced Binary Tree)又称之为AVL树(因为是G.M. Adelson-Velsky 和 E.M. Landis在1962年发明的这棵树)是binary-search tree的严格的平衡版本。

BST达到最平衡的状态称之为AVL。在AVL树中任何节点的两个儿子子树的高度最大差别为一,查找、插入和删除在平均和最坏情况下都是O(lg n)。但因为增加和删除节点可能会破坏“平衡状态”,所以大多数情况下需要通过多次树旋转来重新平衡这个树。所以简单地说,如果你的应用查找次数远远多于增删操作,那么AVL是最好的,但是如果增删次数和查找次数不相上下时,RBT因为相比AVL没有过多的旋转操作,效率要比AVL高。并且在是实际情况中,RBT的应用也更为广泛。至少《intro to algo》这本书上主要讲了RBT。

2. 红黑树几个基本属性:

------------------------

因为RBT是binary-search tree的非严格的平衡版本,所以红黑树继承了BST的基本属性:key值,和基本性质:对于tree中任意节点x,都有x.left.key<x.key<=x.right.key. 此外,BST的叶子节点在RBT不称之为叶子节点,RBT的叶子节点为NIL,所以BST的ROOT节点的父亲节点在RBT表示里不再为空,而是NIL或者称之为TREE.sentinel. BST中的叶子节点也是一样,他们也有孩子为NIL节点。接下来是RBT的独有属性:color,即每个树节点都有自己的color,要么要么

然后是RBT的五个基本性质:1)每个树节点要么是红色要么是黑色;2)ROOT节点必须是黑色;3)NIL节点为黑色;4)如果一个节点是红色,那么它的孩子节点都是黑色(NIL节点除外),从每个叶子到根的所有路径上不能有两个连续的红色节点;5)从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。 如下图所示:

  

3. 旋转:

--------

因为在执行操作或者删除操作以后,RBT的性质可能会被改变。所以为了维护RBT的基本性质[2],我们需要改变一些树的节点的颜色,同时需要改变树的结构。下图展示了树的旋转动作:

          

 旋转动作共有两种:left 和 right。left-rotation 就是逆时针旋转,right-rotation是顺时针旋转。以left-roation为例,如上图右侧所示,

第一步:建立y的左孩子β与x的“亲子关系”:β.parent=x AND x.right=β.

第二步:y取代x:(当x不是ROOT节点时) y.parent=x.parent AND (x.parent.left==x)? (y=x.parent.left): (y=x.parent.right).

(当x是ROOT节点时) y=ROOT

第三步:逆转x,y父子关系: x=y.left AND x.parent=y;

 

 1 LEFT-ROTATE(T, x)
 2 y=x.right
 3 x.right=y.left
 4 if y.left != T.nil
 5         y.left.p=x
 6 y.parent=x.parent
 7 if x.parent==T.nil
 8         T.root=y
 9 else if x==x.parent.left
10         x.parent.left=y
11 else x.parent.left=y
12 y.left=x
13 x.parent=y
View Code

 

 4. 插入:

---------

红黑树的插入和BST基本类似。不清楚BST插入算法的可以参考我的这一篇博客:http://www.cnblogs.com/fu11211129/p/4214047.html

红黑树的插入在BST的插入上做了一些微调,这里把这两种插入的伪代码展示做个比较。

 

TREE-INSERT(T, z)//BST插入算法
1 y=NIL
2 x=T.ROOT
3 while(x!=NIL)
4   y=x
5   if(z.key<x.key)
6     x=x.left
7   else x=x.right
8 z.p=y
9 if(y==NIL) T.ROOT=z
10 else if(z.key<y.key) y.left=z
11 else y.right=z
View Code

 

 

RB-TREE-INSERT(T, z)//RBT插入算法
1 y=T.NIL
2 x=T.ROOT
3 while(x!=T.NIL)
4   y=x
5   if(z.key<x.key)
6     x=x.left
7   else x=x.right
8 z.p=y
9 if(y==T.NIL) T.ROOT=z
10 else if(z.key<y.key) y.left=z
11 else y.right=z
12 z.left=T.NIL
13 z.right=T.NIL
14 z.color=RED
17 RB-INSERT-FIXUP(T, z)
View Code

 

对比两个插入算法,我们可以注意到四个不同,1)BST中的NIL被替换成了T.NIL,这是因为NIL在红黑树体系里是看做一个节点的。2)RBT-INSERT算法里14-15行,我们将T.NIL分别赋值给了z.left和z.right,是为了保持树的合理结构。3)在RBT-INSERT算法里16行将z的颜色设置为红色。4)因为给z着色以后可能会导致红黑树的基本性质被破坏,所以我们调用RB-INSERT-FIXUP函数(下面会具体讲到)来秀姑RBT树使之仍能保持它的基本性质。

5. 插入修复(这里用日本古代武士社会结构做比喻):

-------------------------------------------------

小日本武士中最大的叫将军,管着很多大名,大名是有城有地盘,有兵有旗号,有很多家臣为其服务。家臣又分许多等级,大名的地盘分一部分给家臣,这就是城主。每个家臣都可以有自己的家臣,只要你地盘的收入俸禄养得起。这个等级制度,非常森严,没有出身保证,就算功劳再大也爬不上去。因为除了将军,武士一定得效忠于某个主公的,大名也是将军的家臣。并且这种效忠是继承的,你的子子孙孙都要效忠主公的后代。

日本和中国情况不同,没有人敢喊出王候将相,宁有种乎。所以丰臣秀吉能从,最底层的普通武士做起,一步步做到关白(和将军平级,那时没设将军,相当于将军),成了不可思议的传奇。当与你的功劳或能力所匹配的待遇,超过了主公所能给予,就可能发生改换门庭,甚至颠覆主公的事情。人们都说日本战国时代就是一个下克上的时代。

好了,扯得有点远了,回到正题来。我们把红黑树两种颜色节点,代表两个基本的武士类型,黑色代表安分守已对上机忠心耿耿,但同时也庸碌无为没能力。红色表示既有才能,又有野心。

插入操作时,好像一个新的武士出仕,没有背景,没有功勋。但他有能力,有抱负(红色),就像丰臣秀吉那样。要是跟了个黑色类型的主公,虽然终究会不甘心居人之下,但却没条件“下克上”,因为这个主公对大主公(主公的主公)忠心耿耿,兢兢业业,你再有才也英雄无用武之地,只能待时而动了。这种情况对应着RBT的结构已经稳定。

但要是主公是红色的(有野心),那新武士就有想入非非了,成天跟大主公打小报告,说他谋反。反正主公忠心有限,把柄很多,只是大主公没那个洞察力。大主公看到一些莫须有的证据,谋反这事宁枉勿纵。于是新武士举报有功,主公反倒成了自己的家臣(如下图第二个状态)。但是昏庸的大主公很快就要付出代价,自己成了新武士的下一个目标(如下图第三个状态)。

            

                (B表示新武士,A表示B的主公)

还有一种情况就是,主公的有野心的,但是大主公的其他家臣如果也有野心的话,情况就不一样了,要是其他家臣也一样有能力,那就热闹了,这一家从此两虎相争,不得安宁(此处略去数万字)。大主公能力平庸,控制不住,最终酿成大乱,经过一番血雨腥风,大主公家出现了一位雄才伟略的家主,平定了内乱,原来两家的强势家主全部被消灭,家主换成了忠心可靠(变黑)的人。而大主公家的新家主,野心开始膨涨了(变红)

 

下面走一遍完整的流程:

好了从a)图开始,新武士编号为4(这里我就不标红圈了,阴影的就代表红色)在家臣5的手下做事。家臣5不愿意屈居人下,而4发现了这一点,4想撺掇5去篡权。但不幸的是,同样是7的家臣,家臣8,也是野心勃勃(红色)。所以在大名7的统治下,出现了混乱的局面,群雄四起。。。最终大名家族又出现了一名新的大名7,平定了内乱,将家臣5和8换成了对自己忠心的家臣。而且与此同时,新的大名7也是野心膨胀(变红)。(在途中对应的是状态1经过case1到达状态2)

新的大名7野心勃勃,而且他的主公2也是野心不小,所以大名7只能战而玩阴的了,大名7向大主公11说主公2的坏话。大主公也是二货(黑色),听风就是雨,大名7举报有功,这样一来,主公2反倒成了大名7的下属了。但是很快的,这位二货打主攻11也要自食恶果了,大名7直接占了他的位置。

6. 移植:

--------

因为移植操作是删除操作的一个基本动作,所以这里先做个简要说明。RBT的移植算法基本和BST的一致,大家可以参照博

RB-TRANPLANT(T, u, v)
1 if (u.parent==NIL) T.ROOT=v
2 else if(u==u.parent;.left) u.parent.left=v
3 else u.parent.right=v
4 v.parent=u.parent
View Code

客:http://www.cnblogs.com/fu11211129/p/4214047.html

大家可以发现只有第四行不一样,原因很简单,RBT把NIL看做一个节点了。

7. 删除:

--------

删除操作是比较麻烦的一部分内容,但类似的,和BST删除操作也很相似,http://www.cnblogs.com/fu11211129/p/4214047.html

下面贴出RBT的删除操作的伪代码:

RB-DELETE(T, z)   单纯删除结点的总操作
 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)
View Code

因为相比BST删除算法,就是加入一些节点颜色的处理机制,所以这里不再赘述。大家理解了BST的删除后,RBT也就差不多了。

8. 删除修复(还没有想到合适的比喻,想到了会更新这一部分):

----------------------------------------------------------

还是先贴上伪代码吧,然后结合代码做个分析。

RB-DELETE-FIXUP(T, x)   恢复与保持红黑性质的工作
 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
View Code

前面,我已经说了,因为插入、或删除结点后,可能会违背、或破坏红黑树的原有的性质,所以为了使插入、或删除结点后的树依然维持为一棵新的红黑树,那就要做俩方面的工作:1、部分结点颜色,重新着色 2、调整部分指针的指向,即左旋、右旋。而下面所有的文字,则是针对红黑树删除结点后,所做的修复红黑树性质的工作。

情况1:当前节点的兄弟节点是红色;解法,在当前节点的父节点上进行左旋,结束。此时红黑树性质全部恢复。


情况2:当前节点的兄弟节点是黑色并且兄弟节点的孩子节点也都是黑色 解法:直接把兄弟节点变成红色就行了。

情况3:当前节点的兄弟节点是黑色,且兄弟节点的左孩子为红色,右孩子为黑色。解法:在兄弟节点的左孩子上右旋,并且互换父亲左孩子的颜色。

情况3:当前节点的兄弟节点是黑色,且兄弟节点的两个孩子都为红色。解法: 在父亲节点上左旋,并且互换父亲右孩子颜色。

(日本武士版解释:在介绍插入的时候,我们“插入故事”的“主人翁”是一个刚出道的武士,雄姿英发,羽扇纶巾。。。,在这里我们“删除故事”的主人翁是一个“要退役”的武士x,但是秉承武士“猥琐的”特点,x下岗之前,总要把格局教的混乱不堪才行。但是因为x要“退役”了,所以他自己是没有能力的(黑色)。所以他把目光转移到了他的兄弟上,但这也要看他的兄弟w是个什么货色

如果他胸无大志,那么x就撩起兄弟w的野心(变红)(对应case2)。

如果他的兄弟w是有野心的(红色),x也就不用撺掇了,但是兵法有云,攘外必先安内(我也不知道是不是兵法上的,姑且这么说吧),如果w的两个家臣都安分守己,那么好,w就可以放心去抢班夺权了(对应case1),w成功之后志得意满,生活堕落(变黑),而被w强权的曾经上级B开始卧薪尝胆了(变红)(对应case1)。

如果他的兄弟w胸无大志,但是他的其中一个家臣野心不小,那么x就转而开始撺掇w的家臣犯上作乱(对应case3)。

如果他的兄弟w胸无大志,而且悲催的是,他的家臣没有一个真正沉服于他的。那么x心想我不用攒多了,让他们内斗吧,最后D家族出了一位新的w,先是成功篡权,然后将自己的手下换成对自己中心的人(黑色

 

原文地址:https://www.cnblogs.com/fu11211129/p/4216561.html