红黑树(图文详解+C语言200行代码实现)
红黑树是平衡二叉排序树,既然是二叉排序树,那么红黑树同样具有二叉排序树的所有性质
红黑树的性质
牢记以下五条性质:
- 节点非黑即红
- 根节点是黑色
- 叶子节点(NIL节点)是黑色
- 如果一个节点是红色节点,那么这个节点的子节点都是黑色,也就说不会有相邻的红色节点
- 从根节点出发到叶子节点的路径上,黑色节点的数量相同
第四条和第五条,注定了红黑树中最长路径是最短路径长度的2倍
本质上,红黑树也是通过树高来控制平衡。
红黑树比 A V L AVL AVL树树高控制条件要更松散,红黑树在发生节点插入和删除后,发生调整的概率比 A V L AVL AVL树要更小
调整策略
- 插入调整站在祖父节点向下看
- 删除调整站在父节点看
- 插入和删除的情况一共五种
插入调整的情况
插入调整站在祖父节点向下看
插入调整,是在插入后发生的调整,那么插入的新节点应该是什么颜色?
如果插入的新节点是黑色,岂不是就一定会改变当前插入路径上黑色节点的数量,既然出现了一条黑色节点数量与其他路径黑色节点数量不一样的路径,那不就必然发生调整吗。所以,如果插入的节点为黑色,那么就必然发生调整;如果新插入的是红色节点,那么是不会影响路径上黑色节点的数量的,虽然有可能发生两个红色节点相邻的情况,但是概率是“可能”,如果插入到了黑色节点下方,那就不用调整,只有当红色节点插入到红色节点下方才需要调整。在可能调整和必然调整二者中,当然是选择“可能调整”。所以,新插入的节点就是红色
插入调整主要就是为了解决双红的情况
情况一:叔叔节点为红色
假设 x 为新插入的节点,在插入完成后的回溯过程中,遇到了20节点是否发生调整?答案是:不调整。因为插入调整是站在祖父节点向下看,当前的冲突,不是20节点能处理的冲突。当回溯到15的时候,再向下看两层,会发现自己的子节点和孙子节点发生冲突了,此时才进行调整处理冲突。
当前的图,不要认为它就是一个完整的红黑树,要把它当成一个大的红黑树当中的一棵子树,要把它看成它上边还有树,下边也还有子树
注意理解这句话:关于这一部分,这一部分调整之前,每条路径上黑色节点的数量,应该等于调整之后,每条路径上黑色节点的数量
拿当前这个图来说,调整之前,每个路径上的黑色节点数量为1,那么要求调整之后,每个路径上黑色节点数量也为1
为什么要这么要求?是为了调整后,这个小部分对红黑树其他的部分不产生影响,我们要推导通用的调整策略,而不是对于某一颗树的策略
情况1说的是:我们站在15向下看,15的两个孩子都是红色,并且它有一个孙子也是红色,那么此时一定发生了冲突,构成了以下结构
黑 - 红 - 红结构,黑色节点为每条路径都提供了一个黑色节点,而以下结构,同样是为每条路径都提供了一个黑色节点
所以,当我们位于祖父节点向下看,两个孩子节点都为红色,且有一个孙子节点也为红色,那么就可以将孩子节点都染成黑色,自身再染成红色
思考一个问题:15那个节点一定是黑色吗?答案是一定的,因为在插入新节点之前整个红黑树是平衡的,而15的孩子都是红色,所以15必然是黑色
再思考一个问题:既然当前只是一个大红黑树中的一部分,那么如果15变成红色后,与其父节点冲突了怎么办?答案是:回溯到15的祖父节点再进行处理
情况二:叔叔节点为黑色
LL型:
那么我们该怎么对当前子树进行调整?此时我们应该以20节点作为旋转点进行大右旋,变成下图
可此时依旧不是平衡的,现在我们来想想,在这个子树中,哪些点的颜色是确定的?哪些点的颜色是特例?也就说,发生了LL型失衡,某个节点是一定存在的并且是某个颜色,但是有哪些节点的颜色是特例?
既然是LL型失衡,那么 15节点
和 10节点
的颜色是确定的红色,并且当前是我们讨论的情况二,也就是叔叔节点(uncle)节点是黑色的情况,所以 25节点
也一定是黑色,20节点
的颜色也确定是黑色。因为 25节点
(叔叔节点)是黑色,并且红色节点不能相邻,而又已经有了 10节点
是红色这个失衡的情况,所以 19节点
必然是黑色,5节点
和 13节点
也必然存在且是黑色,那么只有 17节点
是特例。
也就是说,当前情况二的情况下,蓝色三角内的节点必然存在且为当前颜色。
此时,我们将三角内分为两部分,会发现,下半部分全是黑色,而上半部分的三元组(15 - 10 - 20)必须为下半部分的每条路径都提供一个黑色节点。所以,三元组只能变成以下两种情况中的一种
LR型:
21是祖父节点,21的左子节点是红色,左子节点的右子节点是红色,这就形成了LR型的失衡,以 A V L AVL AVL树的调整策略,应该以15为旋转点,进行小左旋,19节点上去了,15节点变成了19节点的左子树,17节点变成了15节点的右子树。但是我们会发现,每条路径上的黑色节点数量依旧没变,所以我们可以得出结论,将以15节点为根节点的子树进行小左旋,并不影响这颗这颗子树每条路径上黑色节点的数量
此时再以21节点为旋转点进行大右旋后
再将顶部三元组转换为以下两种中的一种即可
RR型和RL型同理
删除调整的情况
删除调整站在父节点看
思考:在什么情况下我们才需要进行删除调整?
首先,在删除前红黑树肯定是平衡的,如果我们删除的是红色节点,不会对红黑树的平衡产生影响,显然是不需要调整的,那么在删除什么样的黑色节点的时候才需要调整呢
黑色节点分为度为0,度为1,度为2三种。
-
我们看看删除度为3的节点,红黑树既然具备二叉排序树的所有性质,那么删除度为2的节点其实和二叉排序树一样,找前驱或者后继,然后转换为删除度为0或者度为1的节点即可。
-
我们再来看删除度为1的黑色节点。度为1的黑色节点的唯一子孩子是什么颜色?必然是红色。因为红黑树从根节点出发到每一个叶子节点的路径上的黑色节点数量相同,如果这个度为1的黑色节点的唯一子孩子是黑色,那么它也必须有另一个孩子也是黑色,所以度为1的黑色节点的唯一子孩子只能是红色。当我们删除度为1的黑色节点的时候,这个黑色节点所在路径的黑色节点数量就减1了,所以要把被删除的度为1的黑色节点的孩子变成黑色,再”过继“给被删除节点的父节点,这样就不影响这条路径上黑色节点的数量了。
-
最后看看删除度为0的黑色节点,之前我们从来不去看那个NIL节点,但是在这就得看NIL节点了,一个度为0的黑色节点,它的左右还分别有一个NIL节点,当这个度为0的黑色节点被删除后,它原来的位置也该由一个NIL节点替代,但是此时这条路径上就少一个黑色节点了,所以我们要把这个替代了原来黑色节点的NIL节点,标记为”双重黑“,这时候才需要做删除调整。
插入调整是为了解决红色节点的冲突,删除调整是为了解决”双重黑“节点
情况一:
此时,x点就是”双重黑“节点,我们要站在x节点的父节点向下看(删除调整站在父节点看),43节点一看,诶,我这有个孩子怎么就这么黑呢,不对劲,所以就得处理这个双重黑的x节点。
那么这个情况一就是,双重黑节点的兄弟是黑色,兄弟的两个孩子也都是黑色,父节点加一重黑色,然后双重黑节和双重黑节点的兄弟减一重黑色,那么双重黑节点就变成黑色,兄弟节点就变成红色。
情况二:
此时,x节点为双重黑节点,双重黑节点的兄弟节点为黑色,并且和兄弟节点处于同一边的节点是红色,那么我们就叫做RR型。
以38节点作为旋转点,进行大左旋
那么我们如何修改以51节点为根节点的树上相应节点的颜色,使得修改前这颗子树上每条路径上黑色节点数量和修改后相同?
只需把51节点改为原来根节点的颜色(小左旋之前根节点,也就是38节点),38节点和72节点改为黑色即可,且一定不会发生冲突!此时“双重黑”节点也就被干掉了。
LL型同理
情况三:
此时,x节点为“双重黑”节点,它的兄弟节点为黑色,并且与兄弟节点方向不同的兄弟节点的孩子是红色,那么此时就是RL型。
思考:如果85节点是红色,那么是RR型,还是RL型?答案是:RR型。只要兄弟节点的不同向子节点为红色,同向子节点为黑,那就是RL 或LR型。只要与兄弟节点同向的兄弟节点的子节点是红色,那就是RR或LL型
先以72节点作为旋转点,进行小右旋
那么此时,以51为根节点的子树,有哪些节点的颜色是确定的呢?
51、48、72、64、85节点的颜色是确定的,只有42和73节点的颜色是不确定的
旋转之前,每条路径上是两个黑色节点,旋转之后每条路径上也应该是两个黑色节点。只要把51节点变为黑色,72节点变为红色。
为什么72节点的颜色可以变为红色?本质上还是因为64和85节点的颜色是确定的黑色
此时,就变成了RR型,按照RR型来处理就好
情况四:
大家会发现,我们到目前位置讨论的删除调整的策略,都是双重黑节点的兄弟节点是黑色的情况。那么,如果双重黑节点的兄弟节点是红色怎么办?
我们可以把红色的兄弟节点,旋转到根节点位置,这样,就变成了以上三种情况了
删除调整总结
- 双重黑节点的兄弟节点是黑色,兄弟节点下面的两个子节点也是黑色,父节点增加一重黑色,双重黑与兄弟节点,分别减少一重黑色
- 兄弟节点是黑色,并且,兄弟节点中有红色子节点
1. R(兄弟)R(右子节点),左旋,新根改成原根的颜色,将新根的两个子节点,改成黑色
2. R(兄弟)L(左子节点),先小右旋,对调新根与原根的颜色,转成上一种情况
3. LL 同理 RR
4. LR 同理 RL
- 兄弟节点是红色,通过旋转,转变成兄弟节点是黑色的情况
C语言两百行代码
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int key, color; // 0为红色,1为黑色,2为双重黑
struct Node *lchild, *rchild;
} Node;
Node __NIL;
#define NIL (&__NIL)
__attribute__((constructor))
void init_NIL() {
NIL->key = 0;
NIL->color = 1;
NIL->lchild = NIL->rchild = NIL;
return ;
}
Node *get_new_Node(int val) {
Node *p = (Node *)malloc(sizeof(Node));
p->key = val;
p->color = 0;
p->lchild = p->rchild = NIL;
return p;
}
// 判断是否有红色孩子
int has_red_child(Node *root) {
return root->lchild->color == 0 || root->rchild->color == 0;
}
//左旋
Node *left_rotate(Node *root) {
Node *temp = root->rchild;
root->rchild = temp->lchild;
temp->lchild = root;
return temp;
}
//右旋
Node *right_rotate(Node *root) {
Node *temp = root->lchild;
root->lchild = temp->rchild;
temp->rchild = root;
return temp;
}
//插入调整
Node *insert_maintain(Node *root) {
// 如果当前根节点下根本没有红色子节点,那么就不用调整
if (!has_red_child(root)) return root;
int flag = 0;
// 如果当前根节点的两个子节点都是红色,那么直接改成 红 - 黑 - 黑
// 就算不冲突也不影响平衡,一个偷懒的操作,直接解决叔叔节点为红色情况下的冲突问题
if (root->lchild->color == 0 && root->rchild->color == 0) goto insert_end;
// 叔叔节点为黑色
// 如果当前根节点的左孩子为红色并且左孩子的左孩子也是红色,就是情况一
if (root->lchild->color == 0 && has_red_child(root->lchild)) flag = 1;
// 如果当前根节点的右孩子为红色并且右孩子的右孩子也是红色,就是情况二
if (root->rchild->color == 0 && has_red_child(root->rchild)) flag = 2;
// 如果没有发生冲突,直接返回
if (flag == 0) return root;
if (flag == 1) {
if (root->lchild->rchild->color == 0) {
root->lchild = left_rotate(root->lchild);
}
root = right_rotate(root);
} else {
if (root->rchild->lchild == 0) {
root->rchild = right_rotate(root->rchild);
}
root = left_rotate(root);
}
// 将调整颜色的步骤同一放在最后
insert_end:
root->color = 0;
root->lchild->color = root->rchild->color = 1;
return root;
}
Node *__insert(Node *root, int val) {
if (root == NIL) return get_new_Node(val);
if (root->key == val) return root;
if (val < root->key) root->lchild = __insert(root->lchild, val);
else root->rchild = __insert(root->rchild, val);
return insert_maintain(root);
}
Node *insert(Node *root, int val) {
root = __insert(root, val);
root->color = 1;
return root;
}
//找前驱
Node *predecessor(Node *root) {
Node *temp = root->lchild;
while(temp->rchild != NIL) temp = temp->rchild;
return temp;
}
//删除调整
Node *erase_maintain(Node *root) {
if (root->lchild->color != 2 && root->rchild->color != 2) return root;
if (has_red_child(root)) {
int flag = 0;
root->color = 0;
if (root->lchild->color == 0) {
root = right_rotate(root);
flag = 1;
} else {
root = left_rotate(root);
flag = 2;
}
root->color = 1;
if (flag == 1) root->rchild = erase_maintain(root->rchild);
else root->lchild = erase_maintain(root->lchild);
return root;
}
if ((root->lchild->color == 2 && !has_red_child(root->rchild)) ||
(root->rchild->color == 2 && !has_red_child(root->lchild))) {
root->lchild->color -= 1;
root->rchild->color -= 1;
root->color += 1;
return root;
}
if (root->lchild->color == 2) {
if (root->rchild->rchild->color != 0) {
root->rchild->color = 0;
root->rchild = right_rotate(root->rchild);
root->rchild->color = 1;
}
root = left_rotate(root);
root->color = root->lchild->color;
} else {
if (root->lchild->lchild->color != 0) {
root->lchild->color = 0;
root->lchild = left_rotate(root->lchild);
root->lchild->color = 1;
}
root = right_rotate(root);
root->color = root->rchild->color;
}
root->lchild->color = root->rchild->color = 1;
return root;
}
Node *__erase(Node *root, int val) {
if (root == NIL) return NIL;
if (val < root->key) {
root->lchild = __erase(root->lchild, val);
} else if (val > root->key) {
root->rchild = __erase(root->rchild, val);
} else {
if (root->lchild == NIL || root->rchild == NIL) {
Node *temp = root->lchild != NIL ? root->lchild : root->rchild;
temp->color += root->color;
free(root);
return temp;
} else {
Node *temp = predecessor(root);
root->key = temp->key;
root->lchild = __erase(root->lchild, temp->key);
}
}
return erase_maintain(root);
}
Node *erase(Node *root, int val) {
root = __erase(root, val);
root->color = 1;
return root;
}
void clear(Node *root) {
if (root == NIL) return ;
clear(root->lchild);
clear(root->rchild);
free(root);
return ;
}
void print(Node *root) {
printf("(%d | %d, %d, %d)\n",
root->color, root->key,
root->lchild->key,
root->rchild->key
);
return ;
}
void output(Node *root) {
if (root == NIL) return ;
print(root);
output(root->lchild);
output(root->rchild);
return ;
}
int main() {
int op, val;
Node *root = NIL;
printf("输出格式为:自身颜色 | 自身值,左孩子值,右孩子值\n");
while(~scanf("%d%d", &op, &val)) {
switch(op) {
case 1: root = insert(root, val); break;
case 2: root = erase(root, val); break;
}
output(root);c
printf("----------------\n");
}
clear(root);
return 0;
}