highlight: vs2015
theme: juejin


红黑树的五个性质:

  1. 每个结点是红的或者黑的
  2. 根结点是黑的
  3. 每个叶子结点是黑的
  4. 如果一个结点是红的,则它的两个儿子都是黑的
  5. 对每个结点,从该结点到其子孙结点的所有路径上的 包含相同数目的黑结点

平衡的定义是说从空链接到根节点距离相等,此处一定要用心理解。(也就是说非叶子节点是不会存在空链接的

节点定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#define RED			1
#define BLACK 2
typedef int KEY_TYPE;
typedef struct _rbtree_node {

//红红黑树性质
unsigned char color;
struct _rbtree_node* right;
struct _rbtree_node* left;
struct _rbtree_node* parent;

KEY_TYPE key;

//value
void* value;
} rbtree_node;

typedef struct _rbtree {
rbtree_node* root;
rbtree_node* nil;//哨兵节点
//所有叶子节点都隐藏,并且都设置为黑色
//空节点无颜色,叶子节点有颜色
} rbtree;

rbtree_node* nil;//哨兵节点 因为红黑树的所有叶子节点都隐藏,并且都设置为黑色,因此可以用一个nil统一代表所有的叶子节点 nil节点与NULL不能等同,NULL没有颜色,但是nil节点有颜色

左旋和右旋

左旋rotate 涉及到三个方向,六个指针的改变,

  1. 先将x与b相互指向对方
  2. 再将x的父亲节点与y节点相互指向对方
  3. 再将x与y的方向修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//左旋是以当前节点自己进行下沉
void rbtree_left_rotate(rbtree* T, rbtree_node* x) {

if (x == T->nil)return;

rbtree_node* y = x->right;//y节点

//1 x--b
x->right = y->left;
if (y->left != T->nil) {
y->left->parent = x;
}

//2 x.parent--y
y->parent = x->parent;
if (x->parent == T->nil) {//x为根节点
T->root = y;
}
else if (x == x->parent->left) {
x->parent->left = y;
}
else {
x->parent->right = y;
}

//3 x--y
y->left = x;
x->parent = y;
}

右旋的代码仅需将上述的x改为y,y改为x即可实现

插入节点

在插入节点以前就是一棵红黑树,插入一个节点是红的更容易满足红黑树的性质,插入后当前节点是红色的,父节点也是红色的,只可能违背性质4(如果一个结点是红的,则它的两个儿子都是黑的 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void rbtree_insert(rbtree *T, rbtree_node *z) {

rbtree_node *y = T->nil;
rbtree_node *x = T->root;

while (x != T->nil) {
y = x;
if (z->key < x->key) {
x = x->left;
} else if (z->key > x->key) {
x = x->right;
} else { //Exist
return ;
}
}

z->parent = y;
if (y == T->nil) {
T->root = z;
} else if (z->key < y->key) {
y->left = z;
} else {
y->right = z;
}

z->left = T->nil;
z->right = T->nil;
z->color = RED;

rbtree_insert_fixup(T, z);
}

先是一个while循环找到应该插入的位置,while循环结束x必定为nil节点,因此借助一个临时变量y节点存储插入节点的父节点,找到位置后依据大小将要插入的z节点插入的适当位置,并且将z置为red。再对上述操作进行颜色的修正,而修正函数在如下给出。

插入修正函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
void rbtree_insert_fixup(rbtree *T, rbtree_node *z) {

while (z->parent->color == RED) { //z ---> RED
if (z->parent == z->parent->parent->left) {
rbtree_node *y = z->parent->parent->right;//叔结点
if (y->color == RED) {//叔结点是红色的
z->parent->color = BLACK;
y->color = BLACK;
z->parent->parent->color = RED;

z = z->parent->parent; //z --> RED
} else {

if (z == z->parent->right) {
z = z->parent;
rbtree_left_rotate(T, z);
}

z->parent->color = BLACK;
z->parent->parent->color = RED;
rbtree_right_rotate(T, z->parent->parent);
}
}else {
rbtree_node *y = z->parent->parent->left;
if (y->color == RED) {
z->parent->color = BLACK;
y->color = BLACK;
z->parent->parent->color = RED;

z = z->parent->parent; //z --> RED
} else {
if (z == z->parent->left) {
z = z->parent;
rbtree_right_rotate(T, z);
}

z->parent->color = BLACK;
z->parent->parent->color = RED;
rbtree_left_rotate(T, z->parent->parent);
}
}

}

T->root->color = BLACK;
}

这个过程是一个迭代过程,是自下而上的层层调整,因此需要一个while循环,不论什么时候z节点始终扣住自下而上的那条遍历线路,颜色始终是红色的,z节点调整完之后会变为上层的节点直到根节点也调整完为止

1
2
3
4
5
6
7
rbtree_node *y = z->parent->parent->right;//叔结点
if (y->color == RED) {//叔结点是红色的
z->parent->color = BLACK;
y->color = BLACK;
z->parent->parent->color = RED;
z = z->parent->parent; //z --> RED
}

z = z->parent->parent;这是迭代的关键语句

观察第一幅图,叔父y节点的黑高是2,与其他线的黑高不匹配,因此先对z的父节点123进行左旋变为右侧第二幅图,

1
2
3
4
if (z == z->parent->left) {
z = z->parent;
rbtree_right_rotate(T, z);
}

但是这个时候树高度不平衡了,因此要继续修正,先将z父节点置为黑色,z祖父节点置为红色,在对z的祖父节点右旋即可达到平衡

1
2
3
z->parent->color = BLACK;
z->parent->parent->color = RED;
rbtree_left_rotate(T, z->parent->parent);

以上就是需要修正的3种情况

删除节点

将结点 z 从二叉查找树 T 中删除共有如下三种情况,其中一种有一点难懂。

  • 如果结点 z 没有孩子结点,我们就可以直接使用 NIL 来代替该结点。
  • 如果结点 z 只有一个子女,可以直接用结点 z 的孩子结点替代 z 。
  • 如果结点 z 有两个子女,首先要找到结点 z 的后继 y (它一定在 z 的右子树中),并用 y 替代 z 在树中的位置。

TREE-DELETE 过程用于从二叉查找树 T 中删除一个给定的结点 z ,它按如下四种情况组织代码。

  • 如果结点 z 没有左孩子,那么我们用右孩子替换 z ,其中右孩子可能为 NIL 。右孩子为 NIL 即结点 z 没有孩子结点的情况。(图a)

  • 如果结点 z 有且只有左孩子,那么就用左孩子替代结点 z 。(图b)

  • 否则,结点 z 有两个子结点。首先找到 z 的后继结点 y ,它位于 z 的右子树且没有左孩子。这里我们打算用 y 替换 z 。

    • 如果 y 是 z 的右孩子,那么直接用 y 替换 z 。(图C)
    • 否则,用 y 自己的右孩子替换 y ,再用 y 来替换 z 。(图d)

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
1. rbtree_node *rbtree_delete(rbtree *T, rbtree_node *z) {
2.
3. rbtree_node *y = T->nil;
4. rbtree_node *x = T->nil;
5.
6. if ((z->left == T->nil) || (z->right == T->nil)) {
7. y = z;
8. } else {
9. y = rbtree_successor(T, z);
10. }
11.
12. if (y->left != T->nil) {
13. x = y->left;
14. } else if (y->right != T->nil) {
15. x = y->right;
16. } else {
17. x = T->nil;
18. }
19.
20. x->parent = y->parent;
21. if (y->parent == T->nil) {
22. T->root = x;
23. } else if (y == y->parent->left) {
24. y->parent->left = x;
25. } else {
26. y->parent->right = x;
27. }
28.
29. if (y != z) {
30. z->key = y->key;
31. z->value = y->value;
32. }
33.
34. if (y->color == BLACK) {
35. rbtree_delete_fixup(T, x);
36. }
37.
38. return y;
39. }

image.png

  • 这个代码并不是完全按照算法导论上写的,进行了一些优化
  • 6-10行一个if-else判断直接将四幅图的y都给确定,图a,b中将z赋值给了y。
  • 12-18行的if-else判断直接将四幅图的x都给确定,否则x置为nil,
  • 20-27对应transplant操作,将x与y.parent连接起来了
  • 29-32对应图c,d的情况,将z与x连接后再将y的值赋给z。

    删除修正函数

    红黑树的五个性质:
  1. 每个结点是红的或者黑的
  2. 根结点是黑的
  3. 每个叶子结点是黑的
  4. 如果一个结点是红的,则它的两个儿子都是黑的
  5. 对每个结点,从该结点到其子孙结点的所有路径上的 包含相同数目的黑结点

我们在每次删除前,对于上面前两幅图中的情况,我们会记录下被删除结点z的颜色,因为它决定了我们最后是否需要修正红黑性质(若z为红色,其父结点和孩子必为黑色,删除z将不会违背性质5,用z的孩子去替代z也不会影响性质4),y指向z,x指向z的右孩子;对于后两种情况,我们记录的是被删除结点z右子树中关键字最小的结点y的颜色,同样,如果它是红色,不管z是什么颜色,统一将y的颜色修改为z的颜色,并按照图中的方式去置换掉z,都不会对红黑性质产生影响。综合上述分析,我们发现四种情况的“输出”(处理后的结果)是一致的:若记录的颜色是红色,说明红黑性质未改变,无需修正;如果是黑色,说明红黑性质一定被打破了,需要修正调用fixup函数。
如果结点y是黑色,如下图,将会造成3种影响:

image.png
1)如果y原来是根结点,而y的一个红孩子成为了新的根结点,将会违背性质2根结点是黑的。如上图①中左侧情况。

2)如果x和x的父结点都为红色,将违背性质4如果一个结点是红的,则它的两个儿子都是黑的。如上图③中的左侧,x为红色的情况。

3)所有的情况(除了y原来是根结点外)都将导致之前包含y结点的简单路径上的黑结点数少1,将违背性质5对每个结点,从该结点到其子孙结点的所有路径上的 包含相同数目的黑结点。修正这一问题的方式是我们将现在占据y结点位置的x结点“再涂上一层黑色”,当然,“涂色”操作并不反映在代码上,即我们不会修改x的color属性,我们只是“在心中记住”,适当的时候会把x的这层黑色涂到某个红色结点上以达到目的。“涂了两层色”的x结点可能是双层黑色或红黑色,它们分别会“贡献”2或1个黑色结点数。

下面我们再分析RB-DELETE-FIXUP修正过程:

修正过程分了4种情况,我们先给出每种情况对应的示意图:

image

然后对每种情况给出分析(建立在x是左孩子的基础上):

Case 1:x的右兄弟w是红色,说明x的父结点一定是黑色。所作的操作是:交换w和其父结点的颜色,即把w换为黑色,其父结点换位红色;然后对父结点左旋,w重新指向x的右兄弟(该结点原本是w的左孩子,所以一定为黑色)。这是Case 1过度到Case 2或case 3或 case 4。

Case 2:w的孩子都为黑色(w也是黑色)。所作的操作是:将w换为红色,x指向其父结点。

Case 3:w的左孩子是红色,右孩子是黑色(w也是黑色)。所作的操作是:交换w和其左孩子的颜色,即把w换位红色,其左孩子换为黑色;然后对w右旋,w重新指向x的右兄弟。

Case 4:w的右孩子是黑色(w是黑色)。w与x的父结点交换颜色;并把w的右孩子设为黑色,对x的父结点左旋,x直接指向根结点,循环结束。

做完以上的while循环,我们还要做的一步操作是将根结点置为黑色。这样就能保证满足性质①。

再来看一个具体实例
image.png
上述图解的总体思想是:将情况1首先转换成情况2,或者转换成情况3和情况4。当然,该图解并不意味着调整过程一定是从情况1开始。通过后续代码我们还会发现几个有趣的规则:a).如果是由情况1之后紧接着进入的情况2,那么情况2之后一定会退出循环(因为x为红色);b).一旦进入情况3和情况4,一定会退出循环(因为x为root)。

删除后调整函数fixAfterDeletion()的具体代码如下,其中用到了上文中提到的rotateLeft()rotateRight()函数。通过代码我们能够看到,情况3其实是落在情况4内的。情况5~情况8跟前四种情况是对称的,因此图解中并没有画出后四种情况,读者可以参考代码自行理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
void rbtree_delete_fixup(rbtree *T, rbtree_node *x) {

while ((x != T->root) && (x->color == BLACK)) {
if (x == x->parent->left) {

rbtree_node *w= x->parent->right;
if (w->color == RED) {//1
w->color = BLACK;
x->parent->color = RED;

rbtree_left_rotate(T, x->parent);
w = x->parent->right;
}

if ((w->left->color == BLACK) && (w->right->color == BLACK)) {
w->color = RED;
x = x->parent;//2
} else {

if (w->right->color == BLACK) {
w->left->color = BLACK;
w->color = RED;
rbtree_right_rotate(T, w);
w = x->parent->right;
}

w->color = x->parent->color;
x->parent->color = BLACK;
w->right->color = BLACK;
rbtree_left_rotate(T, x->parent);

x = T->root;
}

} else {

rbtree_node *w = x->parent->left;
if (w->color == RED) {
w->color = BLACK;
x->parent->color = RED;
rbtree_right_rotate(T, x->parent);
w = x->parent->left;
}

if ((w->left->color == BLACK) && (w->right->color == BLACK)) {
w->color = RED;
x = x->parent;
} else {

if (w->left->color == BLACK) {
w->right->color = BLACK;
w->color = RED;
rbtree_left_rotate(T, w);
w = x->parent->left;
}

w->color = x->parent->color;
x->parent->color = BLACK;
w->left->color = BLACK;
rbtree_right_rotate(T, x->parent);

x = T->root;
}

}
}

x->color = BLACK;
}

https://www.cnblogs.com/CarpenterLee/p/5525688.html#4245216