红黑树介绍
红黑树
1 红黑树的引入
有了二叉搜索树,为什么还需要平衡二叉树?
- 二叉搜索树容易退化成一条链,
- 这个时候查找时间复杂度从 退化成
- 引入对左右子树高度差有限制的平衡二叉树,保证查找操作的最坏时间复杂度也为
有了平衡二叉树,为什么还需要红黑树?
- 的左右子树高度差不能超过1,每次进行插入/删除操作时,几乎都需要通过旋转操作保持平衡
- 在频繁进行插入/删除的场景中,频繁的旋转操作使得 的性能大打折扣
- 红黑树通过牺牲严格的平衡,换取插入/删除时少量的旋转操作,整体性能 优于
- 红黑树插入时的不平衡,不超过两次旋转就可以解决;删除时的不平衡,不超过三次旋转就能解决
- 红黑树的红黑规则,保证最坏的情况下,也能在 时间内完成查找操作
红黑树的应用
- Linux 进程调度 CFS
- Nginx Timer 时间管理
- Epoll 事件块的管理
红黑树的性质 (红黑规则)
- 每个节点不是黑的就是红的
- 根节点是黑的
- 每个叶子节点是黑的
- 如果一个节点是红的,则它的两个孩子都是黑的
- 每个节点到叶子节点的所有路径,都包含相同数目的黑色节点(相同的黑色高度)
一些说明
- 约束4和5,保证了红黑树的大致平衡:根到叶子的所有路径中,最长路径不会超过最短路径的2倍。
- 使得红黑树在最坏的情况下,也能有
- 默认新插入的节点为红色:因为父节点为黑色的概率较大,插入新节点为红色,可以避免颜色冲突
- 黑色叶子节点指的是 NULL
2 红黑树的左旋和右旋
红黑树左右旋转是为了恢复黑高
2.1 红黑树的定义
typedef int KEY_TYPE;
#define RED 1 // 黑
#define BLACK 2 // 红
// 定义节点
typedef struct _rbtree_node {
unsigned char color; // 颜色
struct _rbtree_node *right; // 左子树
struct _rbtree_node *left; // 右子树
struct _rbtree_node *parent; // 父亲
KEY_TYPE key; // 键
void *value; // 值
} rbtree_node;
// 定义树
typedef struct _rbtree {
rbtree_node *root; // 跟节点
rbtree_node *nil; // 小技巧,统一空的叶子节点
} rbtree;
- 当红黑规则不满足时,需要对节点进行变色或旋转操作
2.2 左右旋转
左旋代码:
主要思想:右孩子变爹
主要步骤:
- 备份右孩子
- 处理新右孩子
- 给原有孩子认新爹
- 处理处理旋转好后的关系
// T 指的是树, x(原爹)指的对应上图,即轴节点
void rbtree_left_rotate(rbtree *T, rbtree_node *x) {
// 1. 记录 x 的右孩子 y
rbtree_node *y = x->right;
// 2. 处理新右孩子
x->right = y->left; // 然后先处理 x 的右孩子, 即赋值为新的 y->left
if (y->left != T->nil) { // 如果 y->left 存在,记得给他认一下爹,因为红黑树记录了父亲是谁
y->left->parent = x;
}
// 3. 给 y(原右孩子) 认新爹
y->parent = x->parent; // x 的爹现在应该是 y 的爹
if (x->parent == T->nil) { // 如果 x 是根节点,那它没有爹,那么 y 也没有爹,直接变成根节点
T->root = y;
// 否则判断一下是 x 是它爹的左孩子还是右孩子,相应的给 y 认好爹
} else if (x == x->parent->left) {
x->parent->left = y;
} else {
x->parent->right = y;
}
// 4 处理一下旋转好后的 x(原爹) 和 y(新爹) 的关系
y->left = x; // x 是 y 的左孩子
x->parent = y; // x 的爹是 y
}
右旋代码:
技巧: 将左旋的所有left 和 right 互换, x 和 y 互换 即可,因为本来就是镜像的
void rbtree_right_rotate(rbtree *T, rbtree_node *y) {
// 1. 备份左孩子
rbtree_node *x = y->left;
// 2. 处理新左孩子
y->left = x->right;
if (x->right != T->nil) {
x->right->parent = y;
}
// 3. 认爹
x->parent = y->parent;
if (y->parent == T->nil) {
T->root = x;
} else if (y == y->parent->right) {
y->parent->right = x;
} else {
y->parent->left = x;
}
// 4. 调节
x->right = y;
y->parent = x;
}
3 红黑树插入
口诀:左旋/右旋,三个方向,六个指针;
插入的颜色默认是红色,因为红色不会改变黑的高度。
违背的是性质4,当前节点是红色,父节点也是红色。
3.1 先插入
先将新节点插入,主要分为五步:
- 设置节点
- 找爹
- 认孩子
- 完善信息
- 调整树形
// T 是树, z是要插入的节点
void rbtree_insert(rbtree *T, rbtree_node *z) {
// 1. 设置节点
rbtree_node *y = T->nil; // y 表示为 z 的爹,默认为 nil
rbtree_node *x = T->root; // x 表示为根节点
// 2. 找爹
// 找插入的位置, 即找 z 的爹
while (x != T->nil) { // x 存在
y = x; // 用 y 记录 z 的爹
if (z->key < x->key) { // 在根左
x = x->left;
} else if (z->key > x->key) { // 在根右
x = x->right;
} else { // 已经存在了,不要插了,滚
return ;
}
}
// 3. 认孩子
z->parent = y;
if (y == T->nil) { // 没有爹,说明 z 是根节点,即是个空树
T->root = z;
// 有爹,判断自己是爹的左孩子还是右孩子
} else if (z->key < y->key) {
y->left = z;
} else {
y->right = z;
}
// 4. 登记户口,完善信息
// 完善 z 的基本信息
z->left = T->nil;
z->right = T->nil;
z->color = RED;
// 5. 调整
rbtree_insert_fixup(T, z);
}
3.2 再调整
父亲是祖父的左孩子:调整分三个情况
-
叔节点是红色(直接处理)
- 爹、叔叔变黑,爷爷变红
-
叔节点是黑色,当前节点是 右孩子 (左旋爹变成情况3处理)
-
叔节点是黑色,当前节点是 左孩子 (直接处理)
- 爹变黑,爷爷变红,右旋爷爷(目的是为了不改变右子树的黑高,因为爷爷变红了,右子树黑高变低了)
父亲是祖父的右孩子:和左旋右旋的规律一样,左边右,右边左,就行了,还是这三个情况,只不过是镜像的
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; // y是叔节点
// 情况1,直接搞
if (y->color == RED) {
z->parent->color = BLACK; // 父亲变黑
y->color = BLACK; // 叔叔变黑
z->parent->parent->color = RED; // 祖父变红
z = z->parent->parent; //z --> RED
// 情况2,3
} else {
// 情况2,左旋爹变成情况3
if (z == z->parent->right) {
z = z->parent;
rbtree_left_rotate(T, z);
}
// 情况3,直接搞
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;
}
4 红黑树的删除
和插入如出一辙,也可以说简单了,因为虽然是4种情况,实际都是为了最后一种做变换, 其他啥也不是,非常简单,背就完了。
4.1 认清情况
对了,要分清楚我们真正删除的是哪一个就好理解了,例如我们想删除 z = 172,我们是用 y 的值覆盖了 z, 然后调整了 x 的位置。如下图:
4.1 先删除
- 判断z是不是俩孩子都在,都在就得找后继节点y
- 不是都在就 y = z直接删了
- 找到要用来调整的轴心节点 x, 即 y 的孩子
rbtree_node *rbtree_delete(rbtree *T, rbtree_node *z) {
rbtree_node *y = T->nil; // y 是要删除的节点
rbtree_node *x = T->nil; // x 是要调整的轴心节点
// 如果不是左右孩子都存在,需要找后继节点
// 找中序遍历的后继节点,剑指offer原题,即y, 也就是我们要删除的节点
if ((z->left == T->nil) || (z->right == T->nil)) {
y = z;
} else {
y = rbtree_successor(T, z);
}
// 找到 y 的孩子,也就是 x(要调整的轴心节点)
if (y->left != T->nil) {
x = y->left;
} else if (y->right != T->nil) {
x = y->right;
}
// 安顿好 x 的新爹
x->parent = y->parent;
if (y->parent == T->nil) {
T->root = x;
} else if (y == y->parent->left) {
y->parent->left = x;
} else {
y->parent->right = x;
}
// 这里只有在 z 的左右孩子都有的时候才会满足
if (y != z) {
z->key = y->key; // 覆盖节点z
z->value = y->value;
}
// 只有当 y 是黑才调整,因为删除一个红色节点又不影响黑高
if (y->color == BLACK) {
rbtree_delete_fixup(T, x); // 注意要调整的是谁
}
return y; // 返回删除的那个节点,到时候你想free,想干啥都行
}
上面用到的辅助函数:
// 找最左,即最小的那个点
rbtree_node *rbtree_mini(rbtree *T, rbtree_node *x) {
while (x->left != T->nil) {
x = x->left;
}
return x;
}
// 找中序遍历的下一个节点
rbtree_node *rbtree_successor(rbtree *T, rbtree_node *x) {
rbtree_node *y = x->parent;
// 先找右子树上最左的
if (x->right != T->nil) {
return rbtree_mini(T, x->right);
}
// 如果没有右子树,找到第一个是左孩子的爹就行
while ((y != T->nil) && (x == y->right)) { // 只要还是右孩子就一直找,找到是左孩子为止
x = y;
y = y->parent;
}
return y;
}
4.2 再调整
注意要调整的是 x, 然后就没啥了
当前节点是左子树:调整分四个情况
-
兄弟是红色(调整为情况2,3或4)
-
兄弟变黑,父节点变红,左旋父亲,更新兄弟
-
-
兄弟是黑色,兄弟的俩孩子都是黑的(调整为情况3或4)
-
兄弟变红,x指向父节点
-
-
兄弟是黑的,兄弟的左孩子是红的,右孩子是黑的(调整为情况4)
- 左侄子变黑,兄弟变红,右旋兄弟,更新兄弟
-
兄弟是黑的,兄弟的右孩子是红色的,直接干
- 兄父同色,右侄子父亲都变黑,左旋父亲,x指向根节点结束
void rbtree_delete_fixup(rbtree *T, rbtree_node *x) {
// x不是根节点,x是黑才调整
while ((x != T->root) && (x->color == BLACK)) {
if (x == x->parent->left) {// 是左子树
rbtree_node *w= x->parent->right; // 兄弟
// 情况1,调整为情况2、3或4
if (w->color == RED) {
w->color = BLACK; // 兄黑
x->parent->color = RED; // 父红
rbtree_left_rotate(T, x->parent); // 左旋父亲
w = x->parent->right; // 更新兄弟,左侄子变右兄弟了
}
// 情况2,调整为情况3或4
if ((w->left->color == BLACK) && (w->right->color == BLACK)) {
w->color = RED; // 兄弟变红
x = x->parent; // x 指向父节点
} else {
// 情况3,调整为情况4
if (w->right->color == BLACK) {
w->left->color = BLACK; // 兄弟左儿子变黑
w->color = RED; // 兄弟变红
rbtree_right_rotate(T, w); // 右旋兄弟
w = x->parent->right; // 更新兄弟,左侄子成兄弟了
}
// 情况4, 直接干
w->color = x->parent->color; // 兄父同色
x->parent->color = BLACK; // 父亲变黑
w->right->color = BLACK; // 右侄子变黑
rbtree_left_rotate(T, x->parent); // 左旋
x = T->root; // x指向根节点,结束了
}
// 和上面是镜像的
} 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;
}
5 总结
-
调整的原因都是因为违背了性质4或性质5;
- 插入是红色,所以可能违背性质4
- 插入的时候情况2是为了情况3做准备
- 删除可能删除了黑节点,所以可能改变黑高违背性质5
- 删除调整的时候都是在为情况4做准备
- 插入是红色,所以可能违背性质4
-
左旋右旋的目的都是因为黑高改变了需要调整,不信你仔细思考一下,思考不明白文章最后有个很好的参考链接,去看看就知道了
-
另一种都是镜像的,只需要记住一种即可
参考链接
总代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
void *value;
} rbtree_node;
typedef struct _rbtree {
rbtree_node *root;
rbtree_node *nil;
} rbtree;
rbtree_node *rbtree_mini(rbtree *T, rbtree_node *x) {
while (x->left != T->nil) {
x = x->left;
}
return x;
}
rbtree_node *rbtree_maxi(rbtree *T, rbtree_node *x) {
while (x->right != T->nil) {
x = x->right;
}
return x;
}
rbtree_node *rbtree_successor(rbtree *T, rbtree_node *x) {
rbtree_node *y = x->parent;
if (x->right != T->nil) {
return rbtree_mini(T, x->right);
}
while ((y != T->nil) && (x == y->right)) {
x = y;
y = y->parent;
}
return y;
}
void rbtree_left_rotate(rbtree *T, rbtree_node *x) {
rbtree_node *y = x->right; // x --> y , y --> x, right --> left, left --> right
x->right = y->left; //1 1
if (y->left != T->nil) { //1 2
y->left->parent = x;
}
y->parent = x->parent; //1 3
if (x->parent == T->nil) { //1 4
T->root = y;
} else if (x == x->parent->left) {
x->parent->left = y;
} else {
x->parent->right = y;
}
y->left = x; //1 5
x->parent = y; //1 6
}
void rbtree_right_rotate(rbtree *T, rbtree_node *y) {
rbtree_node *x = y->left;
y->left = x->right;
if (x->right != T->nil) {
x->right->parent = y;
}
x->parent = y->parent;
if (y->parent == T->nil) {
T->root = x;
} else if (y == y->parent->right) {
y->parent->right = x;
} else {
y->parent->left = x;
}
x->right = y;
y->parent = x;
}
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; // y是叔节点
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;
}
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);
}
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) {
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;
} 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;
}
rbtree_node *rbtree_delete(rbtree *T, rbtree_node *z) {
rbtree_node *y = T->nil;
rbtree_node *x = T->nil;
if ((z->left == T->nil) || (z->right == T->nil)) {
y = z;
} else {
y = rbtree_successor(T, z);
}
if (y->left != T->nil) {
x = y->left;
} else if (y->right != T->nil) {
x = y->right;
}
x->parent = y->parent;
if (y->parent == T->nil) {
T->root = x;
} else if (y == y->parent->left) {
y->parent->left = x;
} else {
y->parent->right = x;
}
if (y != z) {
z->key = y->key;
z->value = y->value;
}
if (y->color == BLACK) {
rbtree_delete_fixup(T, x);
}
return y;
}
rbtree_node *rbtree_search(rbtree *T, KEY_TYPE key) {
rbtree_node *node = T->root;
while (node != T->nil) {
if (key < node->key) {
node = node->left;
} else if (key > node->key) {
node = node->right;
} else {
return node;
}
}
return T->nil;
}
void rbtree_traversal(rbtree *T, rbtree_node *node) {
if (node != T->nil) {
rbtree_traversal(T, node->left);
printf("key:%d, color:%d\n", node->key, node->color);
rbtree_traversal(T, node->right);
}
}
int main() {
int keyArray[20] = {24,25,13,35,23, 26,67,47,38,98, 20,19,17,49,12, 21,9,18,14,15};
rbtree *T = (rbtree *)malloc(sizeof(rbtree));
if (T == NULL) {
printf("malloc failed\n");
return -1;
}
T->nil = (rbtree_node*)malloc(sizeof(rbtree_node));
T->nil->color = BLACK;
T->root = T->nil;
rbtree_node *node = T->nil;
int i = 0;
for (i = 0;i < 20;i ++) {
node = (rbtree_node*)malloc(sizeof(rbtree_node));
node->key = keyArray[i];
node->value = NULL;
rbtree_insert(T, node);
}
rbtree_traversal(T, T->root);
printf("----------------------------------------\n");
for (i = 0;i < 20;i ++) {
rbtree_node *node = rbtree_search(T, keyArray[i]);
rbtree_node *cur = rbtree_delete(T, node);
free(cur);
rbtree_traversal(T, T->root);
printf("----------------------------------------\n");
}
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性