数据结构与算法——第9章-查找表-红黑树中插入新结点(9.8.3)

一 概述

1
2
3
4
1.红黑树中插入数据的步骤
2.位置情况分析
3.插入位置的双亲结点的颜色为红色的三种情况
4.示例代码

二 红黑树中插入数据的步骤

1
2
3
4
5
6
当创建一个红黑树或者向已有红黑树中插入新的数据时,只需要按部就班地执行以下 3 步:
1.由于红黑树本身是一棵二叉查找树,所以在插入新的结点时,完全按照二叉查找树插入结点的方法,找到新结点插入的位置;
2.将新插入的结点结点初始化,颜色设置为红色后插入到指定位置;(将新结点初始化为红色插入后,不会破坏红黑树第 5 条的性质)
3.由于插入新的结点,可能会破坏红黑树第 4 条的性质(若其父结点颜色为红色,就破坏了红黑树的性质),此时需要调整二叉查找树,想办法

通过旋转以及修改树中结点的颜色,使其重新成为红黑树!

三 位置情况分析

1
2
3
4
5
插入结点的第 1 步和第 2 步都非常简单,关键在于最后一步对树的调整!在红黑树中插入结点时,根据插入位置的不同可分为以下3种情况:
1. 插入位置为整棵树的树根。处理办法:只需要将插入结点的颜色改为黑色即可。
2. 插入位置的双亲结点的颜色为黑色。处理方法:此种情况不需要做任何工作,新插入的颜色为红色的结点不会破坏红黑树的性质。
3. 插入位置的双亲结点的颜色为红色。处理方法:由于插入结点颜色为红色,其双亲结点也为红色,破坏了红黑树第 4 条性质,
此时需要结合其祖父结点和祖父结点的另一个孩子结点(父结点的兄弟结点,此处称为“叔叔结点”)的状态,分为 3 种情况讨论:

四 插入位置的双亲结点的颜色为红色的三种情况

一、情况1

1
2
3
当前结点的父节点是红色,且“叔叔结点”也是红色:破坏了红黑树的第 4 条性质,
解决方案为:将父结点颜色改为黑色;将叔叔结点颜色改为黑色;将祖父结点颜色改为红色;
下一步将祖父结点认做当前结点,继续判断,处理结果如下图所示:

1
2
3
4
分析:此种情况下,由于父结点和当前结点颜色都是红色,所以为了不产生冲突,将父结点的颜色改为黑色。
但是虽避免了破坏第 4 条,但是却导致该条路径上的黑高度增加了 1 ,破坏了第 5 条性质。
但是在将祖父结点颜色改为红色、叔叔结点颜色改为黑色后,该部分子树没有破坏第 5 条性质。
但是由于将祖父结点的颜色改变,还需判断是否破坏了上层树的结构,所以需要将祖父结点看做当前结点,继续判断。

二、情况2

1
当前结点的父结点颜色为红色,叔叔结点颜色为黑色,且当前结点是父结点的右孩子。解决方案:将父结点作为当前结点做左旋操作。

1
提示:在进行以父结点为当前结点的左旋操作后,此种情况就转变成了第 3 种情况,处理过程跟第 3 种情况同步进行。

三、情况3

1
2
当前结点的父结点颜色为红色,叔叔结点颜色为黑色,且当前结点是父结点的左孩子。
解决方案:将父结点颜色改为黑色,祖父结点颜色改为红色,从祖父结点处进行右旋处理。如下图所示:

1
2
3
4
分析:在此种情况下,由于当前结点 F 和父结点 S 颜色都为红色,违背了红黑树的性质 4,此时可以将 S 颜色改为黑色,
有违反了性质 5,因为所有通过 S 的路径其黑高度都增加了 1 ,所以需要将其祖父结点颜色设为红色后紧接一个右旋,
这样这部分子树有成为了红黑树。
(上图中的有图虽看似不是红黑树,但是只是整棵树的一部分,以 S 为根结点的子树一定是一棵红黑树)

五 示例代码

红黑树中插入结点的具体实现代码

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
71
72
73
74
75
76
77
78
79
80
81
82
83
void RB_Insert_Fixup(RBT_Root* T, RB_TREE* x) {
//首先判断其父结点颜色为红色时才需要调整;为黑色时直接插入即可,不需要调整
while (x->p->color == RED) {
//由于还涉及到其叔叔结点,所以此处需分开讨论,确定父结点是祖父结点的左孩子还是右孩子
if (x->p == x->p->p->left) {
RB_TREE * y = x->p->p->right;//找到其叔叔结点
//如果叔叔结点颜色为红色,此为第 1 种情况,处理方法为:父结点颜色改为黑色;叔叔结点颜色改为黑色;祖父结点颜色改为红色,
将祖父结点赋值为当前结点,继续判断;
if (y->color == RED) {
x->p->color = BLACK;
y->color = BLACK;
x->p->p->color = RED;
x = x->p->p;
} else {
//反之,如果叔叔结点颜色为黑色,此处需分为两种情况:1、当前结点时父结点的右孩子;2、当前结点是父结点的左孩子
if (x == x->p->right) {
//第 2 种情况:当前结点时父结点的右孩子。解决方案:将父结点作为当前结点做左旋操作。
x = x->p;
rbTree_left_rotate(T, x);
} else {
//第 3 种情况:当前结点是父结点的左孩子。解决方案:将父结点颜色改为黑色,祖父结点颜色改为红色,从祖父结点处进行
右旋处理。
x->p->color = BLACK;
x->p->p->color = RED;
rbTree_right_rotate(T, x->p->p);
}
}
} else { //如果父结点时祖父结点的右孩子,换汤不换药,只需将以上代码部分中的 left 改为 right 即可,道理是一样的。
RB_TREE * y = x->p->p->left;
if (y->color == RED) {
x->p->color = BLACK;
y->color = BLACK;
x->p->p->color = RED;
x = x->p->p;
} else {
if (x == x->p->left) {
x = x->p;
rbTree_right_rotate(T, x);
} else {
x->p->color = BLACK;
x->p->p->color = RED;
rbTree_left_rotate(T, x->p->p);
}
}
}
}
T->root->color = BLACK;
}
//插入操作分为 3 步:1、将红黑树当二叉查找树,找到其插入位置;2、初始化插入结点,将新结点的颜色设为红色;3、通过调用调整函数,将二叉
查找树重新改为红黑树
void rbTree_insert(RBT_Root**T, int k) {
//1、找到其要插入的位置。解决思路为:从树的根结点开始,通过不断的同新结点的值进行比较,最终找到插入位置
RB_TREE * x, *p;
x = (*T)->root;
p = x;
while(x != (*T)->nil) {
p = x;
if(k<x->key) {
x = x->left;
} else if(k>x->key) {
x = x->right;
} else {
printf("\n%d 已存在\n",k);
return;
}
}
//初始化结点,将新结点的颜色设为红色
x = (RB_TREE *)malloc(sizeof(RB_TREE));
x->key = k;
x->color = RED;
x->left = x->right =(*T)->nil;
x->p = p;
//对新插入的结点,建立与其父结点之间的联系
if((*T)->root == (*T)->nil) {
(*T)->root = x;
} else if(k < p->key) {
p->left = x;
} else {
p->right = x;
}
//3、对二叉查找树进行调整
RB_Insert_Fixup((*T),x);
}

五 参考

  • C语言中文网—红黑树(更高级的二叉查找树)算法详解