为什么会有红黑树?
是因为二叉查找树,最差的情况会退化成链表。查找效率就成了O(n)
为了保证二叉查找树的查找效率,需要保持二叉树的平衡,引出了平衡二叉树,但是完全平衡的二叉查找树实现复杂,每个节点都需要维护一个高度。所以出现了红黑树,它是利用黑高来保持平衡。
红黑树的定义
- 是一个二叉查找树;
- 每个节点都是黑色或者红色;
- 根节点是黑色的;
- 每个叶子节点是黑色的;
- 如果一个节点是红色的,那么它的两个儿子必须是黑色的;
- 从一个节点出发,到任意一个叶子节点,包含相同数目的黑色节点。
红黑树的使用场景
对于红黑树的使用,主要是基于它的两个特点:
- 查找效率O(logn), 适合用于key->value的查找;
- 中序遍历是顺序的。
红黑树典型应用:
- map, 利用了特点1,将key作为索引存入红黑树,通过key可以查找到value。
- nginx中用于定时器等,利用特点2,将定时任务到期的timestamp存入红黑树,通过中序遍历,查找到红黑树中最小的节点,即最近要到期的定时任务。
- cfs(Completely Fair Scheduler, 完全公平调度),利用特点2,将进程调度的时间存入红黑树,查找到最小的节点,即调度时间最短的进程,进行调度,可以做到公平调度。
- 内存管理,利用特点1,可以快速查找到对应的内存块。
内存管理的红黑树的key是内存首地址?
一个内存块的表述方法:
a. 首地址 + 长度
b. 首地址 + 尾地址
红黑树实现
- 定义rbtree_node、rbtree;
- 实现左旋、右旋,用于插入新节点后,继续符合红黑树的条件;
需要动3根线,6个指针。
- 插入新节点,在插入之后进行修正,时的能够继续符合红黑树的定义
新插入的节点,是红色的,所以只有它的父节点是红色的时候,需要修正。
父节点是红色,分成以下case:
- 父节点是祖父节点左子树
- 父节点是祖父节点右子树
上面两个case又可以分为3个小case:
对于父节点是祖父节点左子树的情况,分为以下case:
- 叔父节点是红色;
将父节点和叔父节点设置为黑色,祖父节点设置为红色,这样祖父节点往下就满足红黑树定义。
接着将祖父节点作为当前节点,继续递归进行修正。
- 叔父节点是黑色,当前节点是父节点的右子树
将父节点作为当前节点,并且以它为轴,进行左旋。
- 叔父节点是黑色,当前节点是父节点的左子树
将父节点设置为黑色,祖父节点设置为红色,并以祖父节点为轴进行右旋。
对于父节点是祖父节点右子树的情况,也可以分为3个case,将上面的代码copy一份,将left换成right,right换成left就可以了。
左右子树高度最大相差多少?小于2倍。比如左子树高度为9,那么右子树最小为5,最大相差4
#define RED 0
#define BLACK 1
typedef int KEY_TYPE;
// int key_compare(KEY_TYPE a, KEY_TYPE b) {
// }
// 6项
typedef struct _rbtree_node {
unsigned char color;
struct _rbtree_node *left;
struct _rbtree_node *right;
struct _rbtree_node *parent; // 用于旋转
KEY_TYPE key;
void *value;
} rbtree_node;
typedef struct _rbtree {
rbtree_node *root;
rbtree_node *nil; // 通用的叶子节点,很巧妙
} rbtree;
void _left_rotate(rbtree *T, rbtree_node *x) {
// 3个方向,6根指针
rbtree_node *y = x->right;
// 1
x->right = y->left; // 1.1
if (y->left != T->nil) { // 1.2
y->left->parent = x;
}
// 2
y->parent = x->parent; // 2.1
if (x->parent == T->nil) { // 2.2
T->root = y;
} else if (x == x->parent->left) {
x->parent->left = y;
} else {
x->parent->right = y;
}
// 3
y->left = x; // 3.1
x->parent = y; // 3.2
}
void _right_rotate(rbtree *T, rbtree_node *y) {
// 3个方向,6根指针
rbtree_node *x = y->left;
// 1
y->left = x->right; // 1.1
if (x->right != T->nil) { // 1.2
x->right->parent = y;
}
// 2
x->parent = y->parent; // 2.1
if (y->parent == T->nil) { // 2.2
T->root = x;
} else if (y == y->parent->right) {
y->parent->right = x;
} else {
y->parent->left = x;
}
// 3
x->right = y; // 3.1
y->parent = x; // 3.2
}
void rbtree_insert_fixup(rbtree *T, rbtree_node *z) {
// if (z->parent->color == RED) {
while (z->parent->color == RED) {
if (z->parent == z->parent->parent->left) {
rbtree_node *y = z->parent->parent->right;
if (y->color == RED) { // 把父节点和叔父节点变黑,祖父节点变红
z->parent->color = BLACK;
y->color = BLACK;
z->parent->parent->color = RED;
// 祖父节点的父节点可能是红色的,继续while处理
z = z->parent->parent; //将祖父节点作为当前节点,循环处理
} else {
if (z == z->parent->right) {
z = z->parent;
_left_rotate(T, z);
}
z->parent->color = BLACK;
z->parent->parent->color = RED;
_right_rotate(T, z->parent->parent);
}
} else {
}
}
T->root->color = BLACK; // 中间过程有可能把root节点变为红色了,最后需要将它变黑
}
void rbtree_insert(rbtree *T, rbtree_node *z) {
rbtree_node *x = T->root;
rbtree_node *y = T->nil;
while (x != T->nil) {
y = x; // y是x的父节点
if (z->key < x->key) { // key的比较规则可以交给红黑树的使用者自定义
x = x->left;
} else if (z->key > x->key) {
x = x->right;
} else {
return; // 是否插入相同的key,需要看应用场景
}
}
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);
}