提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
一、维持红黑树的5个性质
1. 根节点是黑色
2. 叶子节点(NIL)是黑色
3. 每个节点非黑即红
4. 如果节点为红色,那么它的两个子节点都是黑色
5. 根节点到每个叶子节点的路径中,黑色节点数量相同
以上性质避免了树退化成单链表的情况,通过性质4和性质5说明红黑树中最长路径是最短路径的2倍,此时在路径最长的情况下 路径上红色节点数量 = 黑色节点数量,长度为只有黑色节点的最短,路径的二倍。
二、红黑树的操作
红黑树的操作和其他树形结构一样,包括查找、插入、删除等。查找过程和二叉查找树一样,比较简单,这里不再赘述。这里着重介绍插入和删除操作。
调整策略
1. 插入调整站在祖父节点向下进行调整
2. 删除调整,要站在父节点向下进行调整
3. 新插入的节点一定是红色,因为插入黑色节点意味着改变某条路径上的黑色节点数量,违反性质5,导致的结果是必然会调整树,插入红色节点,不一定产生冲突
4. 插入调整就是解决双红冲突的情况
5. 把每一种情况想象成一颗大的红黑树中的局部子树
6. 局部调整的时候,为了不影响全局,调整后的路径上黑色节点数量相同
定义空节点
利用__attribute__((constructor))
更改init_NIL
函数的属性,使他在main
函数之前执行。空节点颜色定义为黑色。
//引入NIL节点,代替NULL, NULL不可访问,NIL是一个实际节点可访问
Node __NIL;
#define NIL (&__NIL)
__attribute__((constructor))
void init_NIL() {
NIL->val = 0;
NIL->color = 1;
NIL->lchild = NIL->rchild = NIL;
return;
}
旋转操作 : 旋转操作分为左旋和右旋
左旋 : 以某个节点作为支点(旋转节点),其右子节点变为旋转节点的父节点,右子节点的左子节点变为旋转节点的右子节点
//左旋
Node* left_rorate(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;
}
1.红黑树的插入
插入函数分为两部分,因为要保证根节点为黑色。所以insert函数保证根节点为黑色。__insert作为真正的插入函数。像普通二叉搜索树插入就可以。最后返回的时候要进行调整。
//插入节点
Node* __insert(Node* root, int val) {
if(root == NIL) return getNewNode(val);
if(root->val == val) return root;
if(root->val > val) {
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* getNewNode(int val) {
Node* p = (Node*)malloc(sizeof(Node));
p->val = val;
p->color = 0;
p->lchild = p->rchild = NIL;
return p;
}
插入情况调整
插入是否需要调整调分为两大部分,插入调整是为了解决双红节点:
-
先判断该节点的孩子节点是否有红色节点。没有就返回,有的话。判断左孩子以及左孩子是否有红色节点或者右孩子以及右孩子的孩子节点是否有红色。
-
如果有的话,调整分为三部分。
第一部分判断该节点的左右孩子是否都为红色。是的话。根节点变为红色,两个孩子节点变为黑色。
第二部分左孩子以及左孩子的孩子节点有红色。先判断左孩子的红色节点的孩子是否为右孩子。是的话先对左孩子进行右旋调整。右旋调整右孩子变为根节点,之前的根节点变为右孩子结点的左孩子。右孩子节点的左孩子变为原来根节点的右孩子。之后再进行整体的左旋调整。
第三部分右孩子以及右孩子的孩子节点有红色节点。调整策略与第二部分相反。
-
最后使根节点变为红色,两个孩子节点变为黑色。
情况一
叔叔节点为红色的时候直接修改三元组的颜色为“红黑黑” 1 和 20 修改为黑色 15 修改为红色
情况二
叔叔节点为黑色的时候,参考 AVL树失衡情况, 分成 “LL、 LR、 RR、 RL” 先参考AVL树的旋转策略,然后再修改三元组的颜色, 有两种调整策略 : 红色上浮 和 红色下沉
如果是LR型先将root的左孩子进行左旋转变成LL型再将root进行右旋转
//如果是 LR 型先将root->lchild 小左旋
if(root->lchild->rchild->color == 0) {
root->lchild = left_rorate(root->lchild);
}
//根节点右旋
root = right_rotate(root);
//修改三元组颜色变成“红黑黑”
root->color = 0;
root->lchild->color = root->rchild->color = 1;
LL型直接对root进行右旋
//根节点右旋
root = right_rotate(root);
RL型先将root的右孩子进行右旋变成RR型再将root进行左旋
//如果是RL型 先将root->rchild 小右旋
if(root->rchild->lchild->color == 0) {
root->rchild = right_rotate(root->rchild);
}
//根节点左旋
root = left_rorate(root);
//改变三元组颜色 变成“红黑黑”
root->color = 0;
root->lchild->color = root->rchild->color = 1;
RR型直接对root进行左旋
//根节点左旋
root = left_rorate(root);
插入调整代码为:
//插入调整
Node* insert_maintain(Node* root) {
//如果一个节点它没有红色的子孩子也就没有冲突发生
if(!has_red_child(root)) return root;
//如果节点下面的两个子孩子为红色 由“黑红红” 直接改为“红黑黑”型(红色上浮) 对下面的子树没有影响
if(root->lchild->color == 0 && root->rchild->color == 0) {
root->color = 0;
root->lchild->color = root->rchild->color = 1;
return root;
}
int flag = 0;
// LL 或者 LR 型
if(root->lchild->color == 0&& has_red_child(root->lchild)) flag = 1;
// RR 或者 RL 型
if(root->rchild->color == 0 && has_red_child(root->rchild)) flag = 2;
if(flag == 0) return root;
if(flag == 1) {
//如果是 LR 型先将root->lchild 小左旋
if(root->lchild->rchild->color == 0) {
root->lchild = left_rorate(root->lchild);
}
//根节点右旋
root = right_rotate(root);
//变成“红黑黑”
root->color = 0;
root->lchild->color = root->rchild->color = 1;
} else {
//如果是RL型 先将root->rchild 小右旋
if(root->rchild->lchild->color == 0) {
root->rchild = right_rotate(root->rchild);
}
//根节点左旋
root = left_rorate(root);
//变成“红黑黑”
root->color = 0;
root->lchild->color = root->rchild->color = 1;
}
return root;
}
2.红黑树的删除
分为两部分。第一个删除函数用来控制根节点为黑色。第二个删除函数删除的时候分为删除的节点的出度为0/1、
2.如果出度为2则找到该节点的前驱节点,删除它的前驱节点。度为0/1,则将待删除结点的颜色加到他的子孩子上,返回子孩子。
Node* __erase(Node* root, int val) {
if(root == NIL) return NIL;
if(root->val > val) {
root->lchild = __erase(root->lchild, val);
} else if(root->val < val) {
root->rchild = __erase(root->rchild, val);
} else {
// 节点出度为0/1
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 { // 节点出度为2
Node* temp = predecessor(root);
root->val = temp->val;
root->lchild = __erase(root->lchild, temp->val);
}
}
return erase_maintain(root);
}
Node* erase(Node* root, int val) {
root = __erase(root, val);
root->color = 1;
return root;
}
删除调整发生的前提
1. 删除红色节点,不会对红黑树的平衡产生影响
2. 度为1 的黑色节点,唯一一个孩子一定是红色
3. 删除度为1 的黑色节点,不会产生删除调整,删除节点后唯一的孩子代替父节点位置 颜色置为黑色
4. 删除度为2 的黑色节点,参考AVL树的删除策略, 转化为删除度为1的节点
5. 删除度为0 的节点会产生一个双重黑的NIL节点
6. 删除调整就是为了干掉双重黑
调整策略:
- 先判断该节点有没有双黑节点。
- 有的话判断双黑节点的兄弟节点是不是红色,是红色的话要进行调整,目的是为了下面的颜色调整。调整方法,判断先把根节点变为红色,向双黑节点方向进行调整,再把当前的根节点变为黑色,目的是为了调整前后不改变路径上的黑色节点数量。
- 接下来判断双黑节点的兄弟节点有无红色节点,没有直接根节点颜色加一,孩子节点颜色减一。
- 有的话,要进行调整,解决当双黑节点位于根节点的左侧时,兄弟节点的红色孩子节点位于左侧还是右侧,左侧的话先使根节点变为红色,进行右旋,再使根节点变为黑色目的是不改变调整前后路径上黑色节点数量。之后从根节点进行整体左旋。再将跟节点颜色变为原跟节点颜色。两个孩子节点颜色改为黑色。调整结束。
情况一 双重黑节点的兄弟节点是黑色,兄弟节点下面的两个子节点也是黑色,父节点增加一重黑色,双黑与兄弟节点,分别减少一层黑色
//如果兄弟节点为黑色
//而且兄弟节点孩子中没有红色节点 调整方法 根节点颜色加1 两个子节点颜色减1
if((root->lchild->color == 2 && !has_red_child(root->lchild)) || (root->rchild->color == 2 && !has_red_child(root->lchild))) {
root->lchild->color -= 1;
root->rchild->color -= 1;
root->color += 1;
return root;
}
情况二 双重黑节点的兄弟节点是黑色,并且兄弟节点的子孩子中有红色节点
如果兄弟节点在父节点的右子树上
如果兄弟节点的右孩子为红色节点(RR型): 左旋父节点,新父节点改为原来父节点的颜色,新父节点的两个子节点,改为黑色
如果兄弟节点的左孩子为红色节点(RL型) : 右旋兄弟节点, 对调新兄弟节点颜色和旧兄弟节点颜色, 转化为RR型
LL同理RR
LR同理RL
// 1.如果双重黑节点出现在左孩子中 出现两种情况 右孩子的右孩子为红节点(RR型) 右孩子的左孩子为红节点(RL型)
if(root->lchild->color == 2) {
if(root->rchild->rchild->color != 0) { // RL 型 先将 root->rchild 右旋
root->rchild->color = 0;//新旧节点的颜色交换 旧的节点(root->rchild)设置为新节点(root->rchild->lchild)的颜色 也就是红色
root->rchild = right_rotate(root->rchild);
root->rchild->color = 1;//新节点颜色设置为原节点的颜色也就是黑色
}
// RR型
root = left_rorate(root);//左旋根节点
root->color = root->lchild->color;// 根节点颜色设置为左孩子颜色
}// 2. 如果双重黑节点出现在右孩子中, 同样会出现 LL 型 和 LR 型
else{
if(root->lchild->lchild->color != 0) { // LR 型 先将 root->lchild 左旋
root->lchild->color = 0;//新旧节点的颜色交换 旧的节点(root->lchild)设置为新节点(root->lchild->rchild)的颜色 也就是红色
root->lchild = left_rorate(root->lchild);
root->lchild->color = 1;//新节点颜色设置为原节点的颜色也就是黑色
}
// LL型
root = right_rotate(root);//右旋根节点
root->color = root->rchild->color;// 根节点颜色设置为右孩子颜色
}
//红色上浮 根节点的两个孩子节点颜色置为黑色
root->lchild->color = root->rchild->color = 1;
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_rorate(root);
flag = 2;
}
root->color = 1;//新的根节点为黑色节点
if(flag == 1) { // 双重黑节点在根的右孩子这边
//
root->rchild = erase_maintain(root->rchild);
} else { // flag == 2 双重黑节点在根的左孩子这边
root->lchild = erase_maintain(root->lchild);
}
return root;
}
整体代码演示
/*************************************************************************
> File Name: R_B_tree.cpp
> Author:
> Mail:
> Created Time: Sat 25 Jun 2022 04:55:59 PM CST
************************************************************************/
#include<iostream>
using namespace std;
typedef struct Node{
int val;
int color;// 0 red, 1 black, 2 double black
struct Node* lchild;
struct Node* rchild;
} Node;
Node __NIL;
#define NIL (&__NIL)
__attribute__((constructor))
void init_NIL() {
NIL->val = 0;
NIL->color = 1;
NIL->lchild = NIL->rchild = NIL;
return;
}
Node* getNewNode(int val) {
Node* p = (Node*)malloc(sizeof(Node));
p->val = 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_rorate(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;
//如果节点下面的两个子孩子为红色 由“黑红红” 直接改为“红黑黑”型(红色上浮) 对下面的子树没有影响
if(root->lchild->color == 0 && root->rchild->color == 0) {
root->color = 0;
root->lchild->color = root->rchild->color = 1;
return root;
}
int flag = 0;
// LL 或者 LR 型
if(root->lchild->color == 0&& has_red_child(root->lchild)) flag = 1;
// RR 或者 RL 型
if(root->rchild->color == 0 && has_red_child(root->rchild)) flag = 2;
if(flag == 0) return root;
if(flag == 1) {
//如果是 LR 型先将root->lchild 小左旋
if(root->lchild->rchild->color == 0) {
root->lchild = left_rorate(root->lchild);
}
//根节点右旋
root = right_rotate(root);
//变成“红黑黑”
root->color = 0;
root->lchild->color = root->rchild->color = 1;
} else {
//如果是RL型 先将root->rchild 小右旋
if(root->rchild->lchild->color == 0) {
root->rchild = right_rotate(root->rchild);
}
//根节点左旋
root = left_rorate(root);
//变成“红黑黑”
root->color = 0;
root->lchild->color = root->rchild->color = 1;
}
return root;
}
//插入节点
Node* __insert(Node* root, int val) {
if(root == NIL) return getNewNode(val);
if(root->val == val) return root;
if(root->val > val) {
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) temp = temp->rchild;
return temp;
}
Node* erase_maintain(Node* root) {
//如果节点下面没有双重黑的节点 直接返回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_rorate(root);
flag = 2;
}
root->color = 1;//新的根节点为黑色节点
if(flag == 1) { // 双重黑节点在根的右孩子这边
//
root->rchild = erase_maintain(root->rchild);
} else { // flag == 2 双重黑节点在根的左孩子这边
root->lchild = erase_maintain(root->lchild);
}
return root;
}
//如果兄弟节点为黑色
//而且兄弟节点孩子中没有红色节点 调整方法 根节点颜色加1 两个子节点颜色减1
if((root->lchild->color == 2 && !has_red_child(root->lchild)) || (root->rchild->color == 2 && !has_red_child(root->lchild))) {
root->lchild->color -= 1;
root->rchild->color -= 1;
root->color += 1;
return root;
}
// 如果兄弟节点的孩子中有红色节点
// 1.如果双重黑节点出现在左孩子中 出现两种情况 右孩子的右孩子为红节点(RR型) 右孩子的左孩子为红节点(RL型)
if(root->lchild->color == 2) {
if(root->rchild->rchild->color != 0) { // RL 型 先将 root->rchild 右旋
root->rchild->color = 0;//新旧节点的颜色交换 旧的节点(root->rchild)设置为新节点(root->rchild->lchild)的颜色 也就是红色
root->rchild = right_rotate(root->rchild);
root->rchild->color = 1;//新节点颜色设置为原节点的颜色也就是黑色
}
// RR型
root = left_rorate(root);//左旋根节点
root->color = root->lchild->color;// 根节点颜色设置为左孩子颜色
} // 2. 如果双重黑节点出现在右孩子中, 同样会出现 LL 型 和 LR 型
else{
if(root->lchild->lchild->color != 0) { // LR 型 先将 root->lchild 左旋
root->lchild->color = 0;//新旧节点的颜色交换 旧的节点(root->lchild)设置为新节点(root->lchild->rchild)的颜色 也就是红色
root->lchild = left_rorate(root->lchild);
root->lchild->color = 1;//新节点颜色设置为原节点的颜色也就是黑色
}
// LL型
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(root->val > val) {
root->lchild = __erase(root->lchild, val);
} else if(root->val < val) {
root->rchild = __erase(root->rchild, val);
} else {
// 节点出度为0/1
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 { // 节点出度为2
Node* temp = predecessor(root);
root->val = temp->val;
root->lchild = __erase(root->lchild, temp->val);
}
}
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->val,
root->lchild->val,
root->rchild->val);
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;
while(~scanf("%d%d", &op, &val)) {
switch(op) {
case 1 : root = insert(root, val); break;
case 2 : root = erase(root, val); break;
}
output(root);
printf("--------------\n");
}
return 0;
}
总结
红黑树比AVL树树高控制条件更松散,红黑树在发生节点插入和删除后,发生调整的概率比AVL树更小。