红黑树
1 红黑树的定义
1.1 满足红黑树的条件
红黑树需要满足什么条件呢?
- 根节点和所有外部节点都是黑色;
- 在根节点到外部节点的路径上,没有连续两个红色节点;
- 所有根到外部节点的路径上,黑色节点的数目都相同。
2 红黑树插入节点
2.1 插入节点的颜色
红黑树中插入的节点是什么颜色?
红黑树中插入的新节点都是红色
- 插入的所有节点都是黑色
- 定义3肯定会被破坏,从根到外部节点的路径上,黑色节点的数目变得不相同;
- 插入的所有节点都是红色
- 如果其父节点是红色,则性质2破坏,调整;
- 如果其父节点是黑色,则该树还是红黑树,不调整;
2.2 失衡情况和节点调整
红黑树插入节点如何调整?
父节点是黑色,不做调整;父节点是红色,需要调整
2.2.1 失衡情况
-
LLr
-
LLb
- 其它情况
注意:d在此处都是要删除节点
2.2.2 节点调整
RXX的操作与以下的LXX的操作相反
(1)变色操作
XXr
LRr的变色操作也是一样的
插入的节点是根节点直接变为黑色
//这里的u表视为插入节点
// 获取到插入节点的叔叔节点
rbtree_node* y = u->parent->parent->right;
// LXr ---> 变色
/*
1.u的父亲节点和叔叔节点变为黑色
2.u的祖父节点变为红色
3.将u更新为u的祖父节点 并向上继续检查
*/
if (y->color == RED)
{
u->parent->color = BLACK;
y->color = BLACK;
u->parent->parent->color = RED;
u = u->parent->parent;
}
(2)变色+旋转
XXb
else if (y->color == BLACK)
{
// LRb
/*
先转型为LLb型
- 将 u 的父亲节点更新为 u
- 对 u 进行左旋
[接下来的操作就和LLb一样啦~]
*/
if (u == u->parent->right)
{
u = u->parent;
left_rotate(u, T);
}
// LLb
/*
1.u的父亲节点变为黑色 u的祖父节点变为红色
2.对u的祖父节点为根的子树进行右旋
*/
u->parent->color = BLACK;
u->parent->parent->color = RED;
right_ratate(u->parent->parent, T);
}
3 红黑树删除节点
3.1 大体思路
-
节点颜色:
(1)删除红色节点 —> 无影响 根据下面删除后继节点的方法只改变节点的key值即可;
(2)删除黑色节点 -
节点左右子树:
(1)删除的节点是叶子节点 —> 直接删
(2)删除的节点只有一个孩子 —> 有哪个孩子就用哪个孩子来代替
(3)删除的节点有两个孩子 —> 找(中序遍历)删除节点的后继节点 -
检测是否为红黑树
是红黑树 —> 操作结束
不是红黑树 —> 修改为正规红黑树
3.2 删除节点
3.2.1 删除红色节点
对下图进行具体说明:
- 图上: 要删除20这个节点,该节点的右节点只有一个孩子,对红黑树进行中序遍历(即val值从大到小进行排序),用排序结果易得,20的后继节点为30,所以用30继承20的位置;
- 图下: 要删除20这个节点,但是该节点的右节点有两个孩子,我们不能直接用该节点的右节点去继承,需要通过中序遍历的方法,得到20的后继节点为22,用22代替删除节点;
总之,我们可以通过中序遍历得到删除节点的后继节点,从而用该节点去继承我们要删除的节点
3.2.2 删除黑色节点
我们假设有 u、x、y 三种节点,其分别代表的含义如下图所示:
插入节点u为黑色节点,且x、y节点不可能同时为红色,所以u、x、y的组合有下面有下面几种情况:
u | x | y |
---|---|---|
黑色 | 红色 | 黑色 |
黑色 | 黑色 | 红色 |
黑色 | 黑色 | 黑色 |
注意:我们所要删除的节点u的继承节点x也是通过中序遍历得到的u的后继节点 |
让我们来首先讨论存在红色节点的情况,
(1)存在一个红色节点情况
情况1: 删除节点u直接用u的右子树x来继承u的val即可,color不用发生改变;
情况2: 需要进行两步
- 二叉搜索树删除
- 将y变为黑色
(2)全部都为黑色节点情况
情况3: 相较于前两者情况更为复杂,我们还得对情况3的子集情况进行进一步的讨论
由下图我们可以看出,删除节点40违反了红黑树的性质:
那么我们应该如何去修正呢?我们不妨关注y节点的父亲节点(py)和同辈节点(v),那么我们又可以得到以下四种情况;从图中的文字注释得知,只要我们解决了Rb情况的问题,其它三种情况的问题就能够很好的解决了:
但是的但是(没错还没完~),我们还要对Rb的情况进行更进进一步的讨论,从节点v的角度去分析,我们大致又可以分为以下三种情况:
Rb0
左黑右黑
Rb1
左红右黑
左红右红
Rb2
左黑右红
如何将 Rr 转换成 Rb
4 代码示例
不包括删除节点部分
#include <stdio.h>
#include <stdlib.h>
#define RED 1
#define BLACK 2
typedef int KEY_TYPE;
// 定义红黑树的结构体
typedef struct _rbtree_node
{
unsigned char color; // 红是 1 黑是 2
struct _rbtree_node* right; // 指向右孩子的指针
struct _rbtree_node* left;
struct _rbtree_node* parent; // 指向父亲节点的指针
KEY_TYPE key;
void* value; // 用不到 value
} rbtree_node;
// 再定义一个结构体 保存红黑树的根和外部节点
typedef struct _rbtree
{
rbtree_node* root; // 指向根的指针
rbtree_node* nil; // 外部节点
} rbtree;
// 定义一个函数 生成一个新的节点 给函数节点的key 还有树根
rbtree_node* newNode(rbtree* T, int key)
{
rbtree_node* node = (rbtree_node*)malloc(sizeof(rbtree_node));
// 给新节点赋值
node->color = RED; // 插入红黑树的新节点都是红色
node->value = NULL;
node->left = T->nil;
node->right = T->nil;
node->parent = T->nil;
node->key = key;
return node;
}
// 左旋函数
void left_rotate(rbtree_node* root, rbtree* T)
{
rbtree_node* newroot = root->right;
rbtree_node* T2 = newroot->left;
// root 的右子树变为原来新根的左子树
// newroot->left = root; ---> 把调成放在寻找父节点之后 (因为要)更改父节点
root->right = T2;
// 修改(寻找)父节点 ---> 谁的父节点变了 ---> T2
if (T2 != T->nil)
T2->parent = root;
// 更改新根的父亲节点
newroot->parent = root->parent;
if (root->parent == T->nil)
{
// 原来的 root 就是整个树的根
T->root = newroot;
}
else if (root->parent->left == root)
{
// 原来的树根是左子树 那么新的树根也是左子树
root->parent->left = newroot;
}
else if (root->parent->right == root)
{
root->parent->right = newroot;
}
// 最后 将旧根变成新根的左孩子
newroot->left = root;
root->parent = newroot;
}
// 右旋函数 (和左旋差不多 left 和 right 反过来改就行)
void right_ratate(rbtree_node* root, rbtree* T)
{
rbtree_node* newroot = root->left;
rbtree_node* T2 = newroot->right;
root->left = T2;
if (T2 != T->nil)
T2->parent = root;
newroot->parent = root->parent;
if (root->parent == T->nil)
{
T->root = newroot;
}
else if (root->parent->left == root)
{
root->parent->left = newroot;
}
else if (root->parent->right == root)
{
root->parent->right = newroot;
}
newroot->right = root;
root->parent = newroot;
}
// 定义函数 调整插入后的失衡情况
void rbtree_insert_fixup(rbtree* T, rbtree_node* u)
{ // u 代表插入节点
while (u->parent->color == RED)
{
if (u->parent == u->parent->parent->left)
{
// LXx
// 获取到插入节点的叔叔节点
rbtree_node* y = u->parent->parent->right;
// LXr ---> 变色
/*
1.u的父亲节点和叔叔节点变为黑色
2.u的祖父节点变为红色
3.将u更新为u的祖父节点 并向上继续检查
*/
if (y->color == RED)
{
u->parent->color = BLACK;
y->color = BLACK;
u->parent->parent->color = RED;
u = u->parent->parent;
}
else if (y->color == BLACK)
{
// LRb
/*
先转型为LLb型
- 将 u 的父亲节点更新为 u
- 对 u 进行左旋
[接下来的操作就和LLb一样啦~]
*/
if (u == u->parent->right)
{
u = u->parent;
left_rotate(u, T);
}
// LLb
/*
1.u的父亲节点变为黑色 u的祖父节点变为红色
2.对u的祖父节点为根的子树进行右旋
*/
u->parent->color = BLACK;
u->parent->parent->color = RED;
right_ratate(u->parent->parent, T);
}
}
else
{
// RXx 和 LXx 差不多
rbtree_node* y = u->parent->parent->left;
// RXr ---> 变色 [没啥差别]
if (y->color == RED)
{
u->parent->color = BLACK;
y->color = BLACK;
u->parent->parent->color = RED;
u = u->parent->parent;
}
else if (y->color == BLACK)
{
// RLb
if (u == u->parent->left)
{
u = u->parent;
right_ratate(u, T);
}
// RRb
u->parent->color = BLACK;
u->parent->parent->color = RED;
left_rotate(u->parent->parent, T);
}
}
}
T->root->color = BLACK;
}
// 定义函数 插入新的节点
void rbtree_insert(rbtree* T, rbtree_node* z)
{
rbtree_node* y = T->nil; // 指向我们要插入节点的父节点
rbtree_node* x = T->root; // 指向我们要插入节点的位置
// recursion
while (x != T->nil)
{
y = x;
if (z->key < x->key)
{
x = x->left;
}
else if (z->key > x->key)
{
x = x->right;
}
else
{
return;
}
}
// 找到 z 的父节点
z->parent = y;
// z 插入在 x 的左孩子还是右孩子
if (y == T->nil)
{
// 如果要插入这个节点的父亲节点是 NULL ---> 该节点是整个树的根节点
T->root = z;
}
else if (z->key > y->key)
{
y->right = z;
}
else if (z->key < y->key)
{
y->left = z;
}
// 插入后的数是否是一个红黑树呢?
// 检测 + 调整
rbtree_insert_fixup(T, z);
}
// 中序遍历
void mid_traversal(rbtree* T, rbtree_node* node)
{
if (node == T->nil)
return;
mid_traversal(T, node->left);
printf("key:%d, color:%d\n", node->key, node->color);
mid_traversal(T, node->right);
}
// 先序遍历
void pre_traversal(rbtree* T, rbtree_node* node)
{
if (node == T->nil)
return;
printf("key:%d, color:%d\n", node->key, node->color);
pre_traversal(T, node->left);
pre_traversal(T, node->right);
}
int main()
{
// 10 50 60 62 65 70
int keyArray[6] = { 10, 50, 60, 62, 65, 70 };
// 创建一个红黑树
rbtree* T = (rbtree*)malloc(sizeof(rbtree));
// 定义外部节点[NULL]
T->nil = (rbtree_node*)malloc(sizeof(rbtree_node));
T->nil->color = BLACK;
T->root = T->nil; // 此时当前这个数还是一个空树
// 红黑树插入节点
rbtree_node* node = T->nil;
for (int i = 0; i < 6; i++)
{
node = newNode(T, keyArray[i]);
rbtree_insert(T, node);
}
// 检查
printf("---------中序遍历输出-----------\n");
mid_traversal(T, T->root);
printf("---------先序遍历输出-----------\n");
pre_traversal(T, T->root);
printf("------------End--------------\n");
return 0;
}