您的位置:首页 > 其它

红黑树-详细剖析-如果你有一天或者两天的时间,借助此文,能够学会红黑树的。

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条性质,我想应该是红黑树的创建者们经常长时间的探索总结出来的。

左旋:

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);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐