红黑树
1 概念了解
**用途:**Linux 中的内存管理、进程调度、定时器。
红黑树本质上是一颗二叉树。
**二叉树:**每个节点的儿子都不能多于两个。
二叉树的一个重要应用是它们在查找中的使用,而节点键值没有任何关系的二叉树,查找时间复杂度在最坏情况下是O(n),所以就有了二叉查找树。
**二叉查找树:**使二叉树成为二叉查找树的性质是,对于树中的每个节点 X,它的左子树中的所有关键字值小于 X 的关键字值,右子树中的所有关键字值大于 X 的关键字值。
但二叉查找树容易退化成一个线性序列(当是一个有序序列时),增加时间复杂度O(logn),所以给二叉查找树加入一些规则,使得每个节点的左右子树深度基本维持平衡,优化查找效率,也就是平衡二叉查找树。
二叉查找树:
**平衡二叉查找树(AVL树):**每个节点的左子树和右子树的高度最多差 1 的二叉查找树。
但平衡二叉查找树,为了维持平衡,旋转操作较多,造成开销较大,所以,我们通过牺牲严格的平衡来换取插入操作的简洁,也就是为每个节点赋予红黑两种颜色,而不考虑节点左右子树的深度差**(红黑树)**。
平衡二叉查找树:
其实二叉查找树和红黑树,也就是降低树的深度,优化查找效率。
其实按照红黑树的规则,最长路径不会大于最短路径的两倍(后面说)。
1.1 红黑树约束条件
**红黑树是具有下列着色性质的二叉查找树:**但又不像平衡二叉树有那么严格的平衡条件。
- 每一个节点或者是红色,或者是黑色。
- 根是黑色的(觉得是为了方便插入)。
- 如果一个节点是红色,那么它的子节点必须是黑色的(不能有两个相连的红节点)。
- 从一个节点到一个 NULL 指针的每一条路径必须包含相同数目的黑色节点。
红黑树:
红黑树最长路径就是红黑相间,最短路径就是纯黑(也叫黑高),所以最长不大于最短两倍。确保了红黑树的时间复杂度。
2 自底向上插入
(自底向上插入是插入节点的方法,后面的自顶向下构造红黑树,是构造一颗红黑树。)
-
**新插入节点为黑色:**违反条件 4,因为将会建立一条更长的黑节点路径,因此,这一项必须涂成红色,才有可能不破坏平衡。
-
新插入节点为红色:
2.1 **父节点为黑色:**插入完成。
2.1 **父节点为红色:**得到连续的红色节点,违反条件 3。在这种情况下,我们必须调整该树以确保条件 3 满足(且不引起条件 4 被破坏)。用于完成这项任务的基本操作是颜色的改变和树的旋转。
2.1 父节点为红
因为涉及到旋转操作,不能旋转之后,影响了其它子树,这里涉及到父节点的兄弟节点,我们需要考虑。
先不管为什么要考虑父节点兄弟节点,旋转之后就明白了。
2.1.1 父节点兄弟为黑
令 X 是新加的树叶,P 是它的父节点,S 是该父节点的兄弟(若存在),G 是祖父节点。
在这种情况下,只有 X 和 P 是红的,G 是黑色的,否则就会在插入之前有两个相连的红节点,违反了红黑树的法则。
此时,X 和 P 和 G 会形成一个一字链或之字链。
这里我们画出的是一般的情形,也就是使得 X 在树的中间,其实我们后面构造红黑树的时候,就是这种 X 在中间的情形。
如下所示,是一个最小失衡子树,我们的旋转操作就是在这个最小失衡子树中进行的,所以我们这里给 X 画个子树并不影响。
LL 型:
对应 P 和 G 之间的单旋转,我们管顺时针旋转叫做左旋。旋转按照 AVL 树更好理解。
RR 型:右旋。
LR 型:
对应双旋转,双旋转应该首先在 X 和 P 之间进行,然后在 X 和 G 之间进行。
RL 型:
在上述的情况下,子树的新根均被涂成黑色,因此,即使原来的曾祖节点是红的,我们也排除了两个相邻红节点的可能性。同样重要的是,这些旋转的结果使通向A、B、C诸路径的黑色节点个数保持不变。
到现在为止一切顺利。但是如果 S 是红色的?
2.1.2 父节点兄弟为红
以 LL 型为例:
在这种情况下,初始时从子树的根到C的路径上只有一个黑节点,在旋转之后,一定也只有一个黑节点。
但是在这两种情况下,在通向C的路径上,都有3个节点。由于只能有一个是黑的,又由于我们不能有连续的红节点,于是我们必须把S和子树的新根都涂成红色,而把G以及第四个节点涂成黑色,这样对当前子树来说是平衡的。
此时子树新根为红色,可是如果曾祖也是红色怎么办?
此时我们可以将这个过程朝着根的方向上滤,直到不再有两个相连的红节点,或者到达根节点为止,将根重新涂成黑色。
换个思路想,我们上滤的操作是为了解决新插入节点父节点的兄弟为红色的问题。
而只有P节点是红色的时候会发生旋转,只有S节点是红色的时候才需要去上滤,也就是说,只有当一个节点的两个儿子都是红色的时候,需要上滤操作。
那么我们就可以使用一种自顶向下的方法,去确保 S 不会是红色的。
3 自顶向下红黑树
**上滤操作优化:**上滤操作为了解决新插入节点父节点的兄弟为红色问题。
只有P节点是红色的时候才需要旋转,且S节点是红色的时候需要上滤。所以,只有当一个节点的两个儿子都是红色的时候,需要上滤操作。
这个过程在概念上是容易的,在向下的过程中当我们看到一个节点X有两个红儿子的时候,我们让X成为红的,而让它的两个儿子成为黑的。
只有当翻转后X的父节点P也是红的时候,这种翻转将打破红黑树法则。但是此时呢,我们可以通过上面旋转操作,将树重新平衡。
如果X的父节点的兄弟是红的,会如何,这种可能已经被从顶向下的过程中所排除,因此 X 的父节点的兄弟不可能是红色的!
3.1 插入节点例子
**插入节点 45:**沿树向下,节点 50 有两个红儿子,执行颜色翻转。
**颜色翻转:**颜色翻转之后,节点 50 的父节点也是红色,违反红黑树性质,需进行旋转。
**旋转:**LL型。
**插入:**重新平衡之后,继续沿树向下,到达 NIL 节点,插入 45。若 40 是红的,则再进行一次旋转。
如图所示,所得到的红黑树常常平衡的很好。经验指出,平均红黑树大约和平均 AVL 树一样深,从而查找时间一般接近最优。
红黑树的优点是执行插入所需要的开销相对较低,再有就是实践中所发生的旋转相对较少。
4 红黑树查找
**红黑树查找:**沿树向下即可。
4.1 时间复杂度
O(logN).
**定理:**结点 X 的子树至少包含 2 b h ( x ) − 1 2bh(x)-1 2bh(x)−1 个内结点。( b h ( x ) bh(x) bh(x), 指 X 节点的黑高。)
节点总数: N ≥ 2 b h ( x ) − 1 N ≥ 2bh(x)-1 N≥2bh(x)−1 --> N + 1 ≥ 2 b h ( x ) N + 1 ≥ 2bh(x) N+1≥2bh(x) --> l o g ( N + 1 ) ≥ b h ( x ) log(N +1) ≥ bh(x) log(N+1)≥bh(x).
根据红黑树性质 3: b h ( x ) ≥ h ( x ) / 2 bh(x) ≥ h(x)/2 bh(x)≥h(x)/2.
l o g ( N + 1 ) ≥ b h ( x ) ≥ h ( x ) / 2 log(N+1) ≥ bh(x) ≥ h(x)/2 log(N+1)≥bh(x)≥h(x)/2 --> 2 l o g ( N + 1 ) ≥ h ( x ) 2log(N+1) ≥ h(x) 2log(N+1)≥h(x).
所以,红黑树的高度最多是 2 l o g ( N + 1 ) 2log(N+1) 2log(N+1),则时间复杂度 O ( l o g N ) O(logN) O(logN).
5 代码实现
red_black_tree.h
#ifndef _RED_BLACK_TREE_H_
#define _RED_BLACK_TREE_H_
#define INFINITY 0x7FFFFFFF
#define NEG_INFINITY 0x80000000
typedef enum color {
RED,
BLACK
}color_t;
typedef int element_t;
typedef struct red_black_node {
element_t element;
struct red_black_node *left;
struct red_black_node *right;
color_t color;
}red_black_node_t;
red_black_node_t* red_black_tree_init(void);
red_black_node_t* red_black_node_insert(element_t item, red_black_node_t *rbt);
void print_tree(red_black_node_t *rbt);
red_black_node_t * find(element_t item, red_black_node_t *rbt);
#endif /* _RED_BLACK_TREE_H_ */
red_black_tree.c
#include <stdio.h>
#include <stdlib.h>
#include "red_black_tree.h"
static red_black_node_t *null_node = NULL; /* needs init */
static red_black_node_t *current_node, *parent, *grand, *great;
red_black_node_t* red_black_tree_init(void)
{
red_black_node_t *rbt = NULL;
if (NULL == null_node) {
null_node = (red_black_node_t *)malloc(sizeof(red_black_node_t));
if (NULL == null_node) {
printf("#ERROR:out of space:null_node\r\n");
return null_node;
}
null_node->left = null_node->right = null_node;
null_node->color = BLACK;
null_node->element = INFINITY;
}
/* create the header node */
rbt = (red_black_node_t *)malloc(sizeof(red_black_node_t));
if (NULL == rbt) {
printf("#ERROR:out of space:rbt\r\n");
return null_node;
}
rbt->left = rbt->right = null_node;
rbt->color = BLACK;
rbt->element = NEG_INFINITY;
return rbt;
}
static red_black_node_t* single_rotate_with_left(red_black_node_t *root_subtree)
{
red_black_node_t *child_subtree = root_subtree->left;
root_subtree->left = child_subtree->right;
child_subtree->right = root_subtree;
return child_subtree;
}
static red_black_node_t* single_rotate_with_right(red_black_node_t *root_subtree)
{
red_black_node_t *child_subtree = root_subtree->right;
root_subtree->right = child_subtree->left;
child_subtree->left = root_subtree;
return child_subtree;
}
/**/
static red_black_node_t* rotate(element_t item, red_black_node_t *parent_of_subtree)
{
if (item < parent_of_subtree->element)
return parent_of_subtree->left = (item < parent_of_subtree->left->element) ?
single_rotate_with_left(parent_of_subtree->left) :
single_rotate_with_right(parent_of_subtree->left);
else
return parent_of_subtree->right = (item < parent_of_subtree->right->element) ?
single_rotate_with_left(parent_of_subtree->right) :
single_rotate_with_right(parent_of_subtree->right);
}
static void handle_reorient(element_t item, red_black_node_t *rbt)
{
current_node->color = RED; /* do the color flip*/
current_node->left->color = BLACK;
current_node->right->color = BLACK;
if (RED == parent->color) { /* have a rotate*/
grand->color = RED;
if ((item < grand->element) != (item < parent->element))
parent = rotate(item, grand); /* start double rotation */
current_node = rotate(item, great);
current_node->color = BLACK;
}
rbt->right->color = BLACK; /* make root black */
}
red_black_node_t* red_black_node_insert(element_t item, red_black_node_t *rbt)
{
current_node = parent = grand = rbt;
null_node->element = item;
while (item != current_node->element) { /* descend down the tree */
great = grand;
grand = parent;
parent = current_node;
if (item < current_node->element)
current_node = current_node->left;
else
current_node = current_node->right;
if (RED == current_node->left->color && RED == current_node->right->color)
handle_reorient(item, rbt);
}
if (null_node != current_node) {
return null_node; /*duplicate*/
}
current_node = (red_black_node_t *)malloc(sizeof(red_black_node_t));
if (NULL == current_node) {
printf("#ERROR:out of space:current_node\r\n");
return rbt;
}
current_node->element = item;
current_node->left = current_node->right = null_node;
if (item < parent->element) /* attach to its parent */
parent->left = current_node;
else
parent->right = current_node;
handle_reorient(item, rbt); /* color red, maybe rotate */
return rbt;
}
static void do_print(red_black_node_t *rbt)
{
if (null_node != rbt) {
do_print(rbt->left);
printf("%4d-%d", rbt->element, rbt->color);
do_print(rbt->right);
}
}
void print_tree(red_black_node_t *rbt)
{
do_print(rbt->right);
}
static red_black_node_t * find_item(element_t item, red_black_node_t *rbt)
{
if (null_node == rbt)
return rbt;
if (item < rbt->element)
return find_item(item, rbt->left);
else if (item > rbt->element)
return find_item(item, rbt->right);
else
return rbt;
}
red_black_node_t * find(element_t item, red_black_node_t *rbt)
{
if (NULL == rbt)
return rbt;
return find_item(item, rbt->right);
}
rbt_test.c
#include <stdio.h>
#include "red_black_tree.h"
int main (void)
{
red_black_node_t *rbt = red_black_tree_init();
int rbt_item[] = {10, 85, 15, 70, 20, 60, 30, 50, 65, 80, 90, 40, 5, 55};
for (int item = 0; item < (sizeof(rbt_item) / sizeof(int)); ++item) {
red_black_node_insert(rbt_item[item], rbt);
print_tree(rbt);
printf("\r\n");
}
printf("insert 45:\r\n");
red_black_node_insert(45, rbt);
print_tree(rbt);
printf("\r\n");
printf("find:");
red_black_node_t* find_node = find(90, rbt);
printf("%4d-%d\r\n", find_node->element, find_node->color);
return 0;
}
执行结果:
6 红黑树删除
写的比较仓促。