从0到1手撕红黑树
红黑树:一种自平衡的二叉查找树
一、性质 (平衡条件) (很重要)
- 节点非黑即红
- 根节点是黑色
- 叶子节点(NIL)是黑色
- 红色节点连接的两个子节点均为黑色 -> (红色节点与红色节点不可以相连)
- 从根节点到所有叶子节点的路径上,黑色节点的数量相同
小推论:
性质4+性质5 => 最长边节点数量:最短边节点数量 = 2 :1
某种程度上来说,红黑树也是靠控制树高来控制平衡的
节点定义
typedef struct Node {
int key;
int color; // 0 : red, 1 black, 2 double black
struct Node *lchild, *rchild;
} Node;
引入NIL来代替NULL节点会让对红黑树的操作更加简单
NIL节点定义 + 初始化
Node __NIL;
#define NIL (&__NIL)
__attribute__((constructor))
void init_NIL() {
NIL->key = 0;
NIL->color = 1;
NIL->lchild = NIL->rchild = NIL;
return ;
}
重头戏
一、红黑树的插入:
- 插入节点选择红色节点 (插入红色节点未必需要调整,但是插入黑色节点,由于性质5,一定需要调整,所以插入调整其实就是为了解决双红的问题)
- 插入调整在祖父节点处进行(看子节点与孙子节点是否由双红节点冲突)
插入调整
情况一:
站在18来看,父节点是红色,且叔叔节点为红色
站在15来看,两个子节点是红色,并且,两个子节点中也有红色节点(出现双红现象)
为保持每条路径黑色节点数量相同== 本次采取红色上浮黑色下沉== 如图
修改三元组的颜色 -> 红黑黑
(先不考虑根节点是黑色,将当前的每一棵树都当成一颗子树(这很重要))
情况二:
叔叔节点为黑色
参考AVL树的失衡情况,
分成LL,LR,RL,RR
根据AVL树的旋转调整策略,修改三元组的颜色
对于LL/RR 简单明了,进行大右旋 / 大左旋,调整三元组
本处为LL , 以15进行大右旋, 对三元组进行红色上浮
类比LL不难得出 :遇到RR进行大左旋,然后对三元组进行红色上浮
LR/RL:
这里采用LR举例
在15 处进行小左旋使该树变成LL情况
而后根据LL情况进行大右旋
同理 遇到RL时,先对子树进行小右旋,而后对整个树进行大左旋,然后对三元组进行调整
代码演示
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;
if (root->lchild->color == 0 && root->rchild->color == 0 && (has_red_child(root->lchild) || has_red_child(root->rchild))) {
//叔叔节点为红色节点
root->color = 0;
root->lchild->color = root->rchild->color = 1;
return root;
}
int flag = 0;//用来判断双红冲突节点在左or右
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);//LR小左旋
}
root = right_rotate(root);//LL大右旋
} else {
if (root->rchild->lchild->color == 0) {
root->rchild = right_rotate(root->rchild);//RL小右旋
}
root = left_rotate(root);//RR大左旋
}
root->color = 0;
root->lchild->color = root->rchild->color = 1;//改变三元组的颜色
return root;
}
插入部分代码
Node *__insert(Node *root, int key) {
if (root == NIL) return getNewNode(key);
if (root->key == key) return root;
if (key < root->key) {
root->lchild = __insert(root->lchild, key);
} else {
root->rchild = __insert(root->rchild, key);
}
return insert_maintain(root);
}
Node *insert(Node *root, int key) {
root = __insert(root, key);
root->color = 1;//强制改变根节点为黑色
return root;
}
二、红黑树的删除
- 删除红色节点,不会对红黑树的平衡产生影响
- 度为1的黑色节点,唯一子孩子,一定时红色
- 删除度为1的黑色节点,不会产生删除调整(子节点颜色+1(由红色变为黑色,上来顶替该节点))
- 删除度为0的黑色节点,会产生一个双重黑的NIL节点(下面的一个NIL节点(黑色)颜色+1变为双重黑节点)
- 删除调整,就是为了干掉双重黑
- 删除调整从父节点进行调整
情况一:双重黑的兄弟节点是黑色
1.双重节点的兄弟节点是黑色,并且其兄弟节点的子节点没有红色
调整策略
双重黑节点与其兄弟节点颜色分别减一重黑,父亲节点增加一重黑色
2.双重节点的兄弟节点是黑色,但是其兄弟节点的子节点有红色
以RR举例
因为这里可以确定双重黑的兄弟节点一定为黑色节点,所以左旋89 的左子树,让双重黑的节点,作为根节点,来补上28 的二重黑,28得已减去一重黑可以保持每条路径上的黑色节点数量相同
28减一重黑调整位置任意
分析:
对于左旋之后的树(下图)
在旋转前后可以保证:
因28是双重黑,51是其兄弟节点,可以确定28和51的颜色,一定是黑
因为RR 可以判定72一定是红色, 而又根据红黑树的性质4,可以确定64 和85 一定是黑色
但是38 和 48 的颜色不确定,为了避免 38 和 48 可能引发的双红冲突,将38 和 72 强制改为黑色节点,而51跟随原根节点的颜色
从而48 的颜色随意,推出 => 当遇到双重黑节点的兄弟节点的子节点均为红时,按照RR或者LL来解决,因为另外节点无论时什么颜色,都不会产生影响
调整策略:
RR / LL 右旋 / 左旋, 新根改为原来根的颜色,将新根的两个子节点,改为黑色
以RL举例
对于RL / LR 我们的目的是将其转换为RR / LL 所以对 兄弟节点进行小右旋,原兄弟节点改为红色,新兄弟节点改为黑色
而后根据RR / LL 进行调整
3.特殊情况
兄弟节点为红色节点
调整策略:
抓双黑节点的父节点,向双黑节点方向进行旋转,原根节点改为红节点,新根节点改为黑节点
进行左旋 / 右旋 后,新根节点的左子节点 / 右子节点 为双重黑的父节点,所以站在双重黑的父节点上进行删除调整
删除调整代码
Node *erase_maintain(Node *root) {
if (root->lchild->color != 2 && root->rchild->color != 2) return root;//从n本行以后,存在双重黑节点
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) {//双重黑在左子树节点上
root->lchild->color -= 1;//双重黑节点减一层黑
if (root->rchild->rchild->color != 0) {//RL情况下:先小右旋再大左旋
root->rchild->color = 0;//原兄弟节点改为红色
root->rchild = right_rotate(root->rchild);//小右旋
root->rchild->color = 1;//新兄弟节点改为黑色
}
root = left_rotate(root);//大左旋
root->color = root->lchild->color;//新根的颜色跟随就根的颜色
} else {
root->rchild->color -= 1;//双重黑节点减一层黑
if (root->lchild->lchild->color != 0) {//LR情况下,小左旋
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 key) {
if (root == NIL) return NIL;
if (key < root->key) {
root->lchild = __erase(root->lchild, key);
} else if (key > root->key) {
root->rchild = __erase(root->rchild, key);
} else {
if (root->lchild == NIL || root->rchild == NIL) {//删除度为0或者1的节点
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 key) {
root = __erase(root, key);
root->color = 1;
return root;
}
红黑树整体代码
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int key;
int color; // 0 : red, 1 black, 2 double black
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 ;
}
void output(Node *);
Node *getNewNode(int key) {
Node *p = (Node *)malloc(sizeof(Node));
p->key = key;
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;
if (root->lchild->color == 0 && root->rchild->color == 0 && (has_red_child(root->lchild) || has_red_child(root->rchild))) {//双红节点,并且根节点也是红的时候
root->color = 0;
root->lchild->color = root->rchild->color = 1;
return root;
}
int flag = 0;
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->color == 0) {
root->rchild = right_rotate(root->rchild);
}
root = left_rotate(root);
}
root->color = 0;
root->lchild->color = root->rchild->color = 1;
return root;
}
Node *__insert(Node *root, int key) {
if (root == NIL) return getNewNode(key);
if (root->key == key) return root;
if (key < root->key) {
root->lchild = __insert(root->lchild, key);
} else {
root->rchild = __insert(root->rchild, key);
}
return insert_maintain(root);
}
Node *insert(Node *root, int key) {
root = __insert(root, key);
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;//从n本行以后,存在双重黑节点
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) {//双重黑在左子树节点上
root->lchild->color -= 1;//双重黑节点减一层黑
if (root->rchild->rchild->color != 0) {//RL情况下:先小右旋再大左旋
root->rchild->color = 0;
root->rchild = right_rotate(root->rchild);//小右旋
root->rchild->color = 1;
}
root = left_rotate(root);//大左旋
root->color = root->lchild->color;
} else {
root->rchild->color -= 1;//双重黑节点减一层黑
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 key) {
if (root == NIL) return NIL;
if (key < root->key) {
root->lchild = __erase(root->lchild, key);
} else if (key > root->key) {
root->rchild = __erase(root->rchild, key);
} else {
if (root->lchild == NIL || root->rchild == NIL) {//删除度为0或者1的节点
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 key) {
root = __erase(root, key);
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->key, root->color, root->lchild->key, root->rchild->key);
}
void output(Node *root) {
if (root == NIL) return ;
output(root->lchild);
print(root);
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;
case 3: output(root);
}
// output(root);
// printf("-----------\n");
}
return 0;
}
如果看明白了,不妨自己手撕一下 红黑树oj 检查一下qwq
代码中的找前驱代码为不完美代码,有bug 如果有什么改进的想法,欢迎各qwq