定义:
1.每个节点不是红色就是黑色;
2.根节点是黑色
3.如果一个节点是红色,则他的两个孩子节点是黑色(从每个叶子到跟的所有路径不能有两个连续的红色节点)
4.对于每个节点,从该节点到其所有后代节点的简单路径上,均包含相同数目的黑色节点
5.每个叶子节点都是黑色的(此处的叶子节点是值空节点如下图解释,看似24是叶子节点,但对于红黑树来说,最后的两个空子节点才是叶子节点)
二叉树节点和二叉树的代码定义
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;
二叉树的初始化创建
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; // 初始化根节点
在分析红黑树的插入之前,需要先了解平衡二叉树的旋转:左旋、右旋。在红黑树的插入后,可以通过旋转来维持红黑树的平衡特性
旋转是二叉树的特性,并不是红黑树的特性,需要注意的是,旋转的前后,二叉树的有序性不能被破坏,即旋转前后二叉树的左子树小于根,小于右子树(当然你的二叉树也可以是左子树大于跟,大于右子树)
已左旋为例:x的右子树指向y的左子树,y的左子树的父指向x,y的左子树指向x,y的父指向x的父,x的父的孩子(左或者右,需要判断)指向y,x的父指向y
右旋和左旋逻辑一样,左右旋代码实现如下:
void _left_rotate(rbtree *T, rbtree_node *x) {
rbtree_node *y = x->right;
x->right = y->left;
if (y->left != T->nil) {
y->left->parent = x;
}
y->left = x;
y->parent = x->parent;
if (x->parent == T->nil) {
T->root = y;
} else if (x->parent->left == x) {
x->parent->left = y;
} else {
x->parent->right = y;
}
x->parent = y;
}
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(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); // 修复平衡
}
修复平衡分析:
插入后分为以下几种情况
1.插入节点的父节点为黑,这种情况直接插入,不用做任何修复
2.插入节点的父节点为红,这种情况需要详细分析
分析第二种插入情况
1.在插入当前节点前,T就是一个红黑树,既满足红黑树左右条件,这是一个很重要的点
2.由第一条结合红黑树特性可以推出,插入节点的祖父节点为黑色
3.插入节点的父节点有可能是祖父的左子树或者右子树,先以左子树为例子进行分析
4.假设插入节点的父节点是344或者374, 插入节点的叔父有两种可能,红或者黑,先说如果叔父为黑的情况,因为在插入该节点前为红黑树,所以如果在叔父为黑且不为叶子节点的时候,是不可能达成红黑树特性的,因为插入节点的父节点为红,插入节点的叔父节点和父节点在插入节点的祖父向上,父节点和祖父节点的黑高肯定一样,假设为x,因为父节点有两个黑的子节点,所以黑高为x+1,如果叔父为子节点,那么叔父那边的黑高也为x+1,符合红黑树性质,如果叔父为黑,且不为叶子,那么黑节点的叶子也为黑,叔父这边的黑高肯定大于x+1,所以得出结论:如果叔父节点为黑,必为叶子节点
5.第四步分析了叔父为黑的情况,如果叔父节点为红色,那么需要做的是重新染色,保证黑高和没有连续红节点,做法是:父和叔父染为黑色,祖父染为红色,然后需要将祖父设置为需要修复的节点,递归像上处理
6.叔父节点为黑色的时候,如果添加的节点在右子树,先对添加节点的父进行左旋,然后重新染色,在对祖父节点进行右旋
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;
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;
}