红黑树性质
红黑树是一颗自平衡二叉查找树,相较于普通的二叉查找树,红黑树左右子树更加平衡
- 每个节点是红色的或者黑色的
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个儿子都是黑色的。
- 对每个节点,从该节点到其子孙节点的所有路径上的包含相同数目的黑色节点。
对于红黑树来说,所有的叶子节点都是黑色并且在图中隐藏,下图就是一颗红黑树:
在上图中,我们发现所有的叶子节点都隐藏起来了,颜色为黑色,例如key为232的节点就有两个儿子,两个儿子都为叶子节点。
红黑树的应用
我们知道红黑树有很多应用,开源项目中同时也有很多红黑树的应用:
- Linux进程调度
- Nginx Timer事件管理
- Epoll事件块管理
红黑树定义
#define RED 1
#define BLACK 2
typedef int KEY_TYPE;
typedef struct _rbtree_node //红黑树节点
{
unsigned char color; //节点颜色
_rbtree_node* left; //左儿子
_rbtree_node* right; //右儿子
_rbtree_node* parent; // 父亲
void* value; //节点所带的值
KEY_TYPE key; // 节点关键字
} rbtree_node;
typedef struct _rbtree //红黑树
{
rbtree_node* root; // 红黑树树根
rbtree_node* nil; // 所有隐藏的黑色节点(叶子节点)所指向的节点 其实相当于一个空节点 写出来方便大家理解
} rbtree;
这里给出红黑树的代码定义片段
红黑树的插入
对于一颗普通的二叉查找树来说,树的结构取决于插入的顺序。例如按顺序插入值为1,2,3,4,5,6的节点,最后就会一颗深度为6的二叉树。这对于查找来说无疑是不理想的,红黑树的定义就解决了这个痛点。
由于一颗红黑树需要满足:
- 每个节点是红色的或者黑色的
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个儿子都是黑色的。
- 对每个节点,从该节点到其子孙节点的所有路径上的包含相同数目的黑色节点。
所以在插入的时候,我们需要不断的调整树的结构,以及变换树的颜色。
调整红黑树节点结构
对于红黑树节点的插入,我们有两种方式来调整树的结构。
- 左旋
- 右旋
对着上图,我们就来谈谈其中的左旋
左旋分为三个步骤:
首先,左旋一定是父节点的右儿子绕父节点进行左旋。右旋一定是父节点的左儿子绕父节点进行右旋。
- x节点的右儿子指向y的左儿子,y的左儿子的父亲指向x节点(但是要注意y的左儿子是不是叶子节点,如果是叶子节点,就统一指向二叉树的nil节点)
x的父亲节点指向y节点,y的父亲节点指向x的父亲节点。
- 其中如果x为x的父亲节点的左儿子,那么x父亲节点左儿子指向y
- 其中如果x为x的父亲节点的右儿子,那么x父亲节点右儿子指向y
- 如果x为根节点,那么y为根节点。
- y的左儿子指向x,x的父亲节点指向y。
经过这三步,我们就可以把上图左边的树变成右边的树
附上左旋和右旋的相关代码
//只旋转,不改变颜色
void rbtree_left_rotate(rbtree* T, rbtree_node* x)
{
rbtree_node* y = x->right;
// x的父节点指向y y的父节点指向x的父节点
// 先判断x是父节点的左儿子还是右儿子
if (x->parent == T->nil)
{
T->root = y;
}
else if (x == x->parent->left)
{
x->parent->left = y;
}
else
{
x->parent->right = y;
}
y->parent = x->parent;
//x的右儿子指向 y的左儿子
//y的左儿子的父亲节点 指向x的右边的儿子
x->right = y->left;
if (y->left != T->nil)
{
y->left->parent = x;
}
//y的左儿子指向x x的父亲指向y
y->left = x;
x->parent = y;
}
void rbtree_right_rotate(rbtree* T, rbtree_node* x)
{
rbtree_node* y = x->left;
//x的左儿子指向y的右儿子
//y右儿子的父亲指向x的左儿子
x->left = y->right;
if (y->right != T->nil) //T->nil 为空
{
y->right->parent = x->left;
}
// x的父亲指向y
//y的父亲指向x的父亲
if (x->parent == T->nil)
{
T->root = y;
}
else if (x->parent->left == x)
{
x->parent->left = y;
}
else
{
x->parent->right = y;
}
y->parent = x->parent;
// y的右儿子指向x
//x的父亲指向y
y->right = x;
x->parent = y;
}
调整红黑树节点颜色
红黑树插入,最重要的就是颜色的更改,因为颜色关乎红黑树的性质,性质3和性质4决定了我们如何调整红黑树的颜色和结构,调整颜色又分为4种情况,在介绍4种情况之前,我们要了解插入的节点应该是什么颜色?为了满足性质4,我们应该插入的节点的颜色是红色。并且我们假设再插入之前我们面对的是一颗红黑树,了解了这一点,我们将介绍4种不同的情况
父节点是黑色的情况
由于父节点是黑色,插入的节点又是红色,那么红黑树的性质没有被破坏,也就不需要调整
父节点是红色,同时是祖父节点左子树的情况
在这种情况下,我们面临三种情况
1.叔节点(父亲的兄弟)是红色的。
在这种情况下,我们只需要把祖父节点颜色变红,同时将父节点和叔节点变黑。注意需要循环执行,直到当前节点的父节点颜色是黑色,当前节点也就是z
2.叔节点是黑色,当前节点是右孩子
这种情况我们需要将z节点绕父节点左旋。将当前节点的父节点变成z(z为我们应该指向的节点),这样情况就变成了下面的情况。
3.叔节点是黑色,当前节点是右孩子
在这种情况下,我们将z节点的父亲节点绕z节点父亲节点的父亲节点进行右旋。然后将父节点变为黑色,旋转前的父亲节点的父亲节点变为红色。注意需要循环执行,直到当前节点的父节点颜色是黑色为止
父节点是红色,同时是祖父节点右子树的情况
和上面的情况相同,也分为三种小的情况,读者可以自行分析。
talk is cheap ,show me the code
void rbtree_insert_fixup(rbtree* T, rbtree_node* z)
{
//递归处理当前节点为红色的情况
while (z->parent->color == RED)
{
if (z->parent == z->parent->parent->left)
{
rbtree_node* uncle = z->parent->parent->right;
if (uncle->color == RED)
{
z->parent->parent->color = RED;
z->parent->color = BLACK;
uncle->color = BLACK;
z = z->parent->parent; // 改变了z->parent->parent的颜色为红色,并且没有改变路径上的黑色节点个数 接下来需要循环处理
}
else
{
if (z->parent->right == z)
{
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* uncle = z->parent->parent->left;
if (uncle->color == RED)
{
uncle->color = BLACK;
z->parent->color = BLACK;
z->parent->parent->color = RED;
z = z->parent->parent;
}
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;
//判断插入的位置 循环结束后 x为空 y指向x的父亲
while (x != T->nil)
{
y = x;
if (x->key > z->key)
{
x = x->left;
}
else if (x->key < z->key)
{
x = x->right;
}
else
{
std::cout << "关键字重复" << std::endl;
return; //节点存在 就不插入了 因为关键字唯一
}
}
z->parent = y;
if (y == T->nil)
{
T->root = z;
z->color = BLACK;
}
else if (z->key > y->key)
{
y->right = z;
}
else
{
y->left = z;
}
// z的左儿子和右儿子成为了 新的叶子节点颜色为黑色
z->left = T->nil;
z->right = T->nil;
z->color = RED;
//插入的节点为红色不会改变红黑树的定义(树根到叶子节点的黑色节点不会改变)
//但是由于插入红色节点位置,其父节点也可能为红色,违反了定义,接下来要改变节点的颜色
rbtree_insert_fixup(T, z);
}
写到这里,红黑树的插入就大概差不多啦,完整代码如下附上:
https://paste.ubuntu.com/p/St...
是可以直接运行的,代码有些细节问题,读者需要注意一下,比如旋转的时候,如果有节点是叶子节点的时候,应该如何处理(叶子节点都是黑色且用nil代替表述,也就是说所有的叶子节点都对应着nil这一个节点,同时nil父亲节点为空,因为nil的父亲太多啦 就不需要指向特定的父亲节点)。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。