1. 红黑树的原则
1.每个节点要么是黑色,要么是红色
2.根节点必须是黑色
3.每个空的叶节点是黑色(NIL或者NULL)是黑色
4.新插入的节点是红色
5.红色节点不能连续(也就是,红色节点的孩子和父亲都不能是红色)(这个原则主要用于节点插入时树的调整)
6.每个节点,从任一节点到其nuLL的任何路径,都含有相同个数的黑色节点(也就是相同的黑色节点高度)
原则五的意思是任何一个子树,到null的黑色节点是相同的。(这个原则用于节点的删除时,对树的调整)
2. 红黑树的结构调整
红黑树的结构调整,主要是通过左旋或者右旋进行的。
2.1 左旋
被旋转的节点记为x,左旋就是将x节点的右孩子与x节点交换,左旋就是将x节点变为左节点。分为三步完成。
(1) x节点的右孩子(xr)的左孩子(xrl)将变成x节点的右孩子(xr)
(2)x节点的右孩子(xr)成为x节点的父亲的孩子(也就是x与xr的交换)
(3)x节点成为其原右孩子的左孩子。
static void rotate_left(struct entry ** root, struct entry * x)
{
struct entry * r = NULL;
if((NULL != *root) &&(NULL != x)) {
r = x->right; //1.保存x节点的右子树
x->right = r->left;//2.x节点的右孩子换为其原来右子树的左孩子
if(NULL != r->left)
r->left->parent = x;//3.将x右子树的左孩子的父节点设为x ,这一步完成了
//2,3两步完成了将x节点右子树的左孩子换为x节点的右子树
r->parent = x->parent;//4.将x节点的父节点设为其右子树的父节点
if(x->parent == NULL) {//5.说明x节点是跟节点,进行相关操作
x->is_root = false;
r->mod_count = x->mod_count;
r->e_size = x->e_size;
if(x->cmp != NULL)
r->cmp = x->cmp;
//上面5行完成了将根节点交接到r节点(x节点的右孩子)
r->is_root = true;//设置r为根节点
//r->color = BLACK;
*root = r;
} else if(x->parent->right == x) //6.x节点是右孩子,那么就将r节点设为x父节点的右孩子
x->parent->right = r;
else
x->parent->left = r;//7.x节点是左孩子,那么就将r节点设为x父节点的左孩子
//4,5,6,7 这几步,主要完成x父节点与r的连接
r->left = x;//8.r的左孩子为x
x->parent = r;//9x的parent为r
//8,9 这两个,完成将x节点设置为r节点的左孩子
}
}
2.2 右旋
右旋跟左旋对应,右旋将x节点与x节点的左孩子交换,x节点将变为右节点。
(1)x节点的左孩子的右孩子(XLR)变为x的左孩子
(2)x节点的左孩子成为x节点父亲节点的孩子(也就是x节点与xL进行交换)
(3)x节点成为x原来左孩子的右孩子
static void rotate_right(struct entry ** root, struct entry * x)
{
struct entry * l = NULL;
if((NULL != *root) && (NULL != x)) {
l = x->left;//1.保存x节点的左子树,记为l
x->left = l->right;//2.x节点的左孩子(l)的右孩子换为x的左孩子
if(NULL !=l->right)
l->right->parent = x;//3.x节点的左孩子(l)的右孩子的父节点换为x
//2,3步完成将x的左孩子的右孩子换为x的左孩子
l->parent = x->parent;//4.x节点的左孩子的父节点,换为x的父节点
if(NULL == x->parent) {//5.父节点为根节点
x->is_root = false;
l->mod_count = x->mod_count;
l->e_size = x->e_size;
if(x->cmp != NULL)
l->cmp = x->cmp;
l->is_root = true;
//l->color = BLACK;
*root = l;
} else if (x->parent->right == x)//6.x是右孩子,则x左孩子成为x父节点的右孩子
x->parent->right = l;
else
x->parent->left = l;//7.x是左孩子,x左孩子成为x父节点的左孩子
//4,5,6,7 几步完成了将x节点与x左孩子的对接
l->right = x;//8.x成为x左孩子的右孩子
x->parent = l;//9.x节点的父节点设置为x左孩子
//8,9两步完成了将x设置为其左孩子的右孩子
}
}
3.节点的插入
节点插入,新加入的节点是红色,所以如果父节点是红色的话,违反红黑树的原则5(红色节点不得连续),这时需要根据情况进行相关节点的颜色调整或者树的结构进行调整。
有以下的三种情况。
情况一:被插入的节点是根节点
处理方法:直接把此节点涂为黑色
情况二:被插入的节点的父节点是黑色
处理方法:什么都不需要做,因为插入是红色,父节点是黑色,仍然满足红黑树
情况三:被插入的节点的父节点是红色
处理方法:颜色调整或结构调整
调整的原则是:先调颜色后调结构
从树末(新加入点)到树根的调整,即先把先加入的父父节点为根的小树调整为满足规则,在按相同的规则逐一往上进行调整。
1).叔父节点为红色时,进行颜色调整(将父辈(父亲和叔父),以及祖父辈的颜色翻转),此时小树满足颜色要求,就将新加入的x节点转为为祖父节点,查看祖父节点所在的小树是否满足红黑颜色调整。
2)颜色调整的结束条件是叔父节点为黑色,此时颜色已经满足要求,调整树的结构来达到平衡(满足其他的要求)
3)结构调整的原则
A.x节点与父节点有相同的结构属性(比如都是左孩子或者都是右孩子):进行一种旋转
需要根据x节点的结构属性(左或者右)对x的祖父节点进行旋转
B.x节点与父节点的结构属性不相同(x是左,p是右):进行两种旋转
根据x的结束属性的相反值,对父节点进行旋转,并且x转为x的父节点
进行A情况的旋转。
上图从截取:http://www.cnblogs.com/CarpenterLee/p/5503882.html
static void fix_after_insertion(struct entry ** root, struct entry * x)
{
struct entry * p = NULL;
struct entry * pp = NULL;
struct entry * ppr = NULL;
struct entry * ppl = NULL;
while((NULL != x) && (!(x->is_root)) && (x->parent->color == RED)) {
p = x->parent; //父节点
pp = p->parent;//父父节点
ppr = pp->right;//父父节点的右孩子
ppl = pp->left;//父父节点的左孩子
if(p == ppl) {//父节点是父父节点的左孩子
if((NULL != ppr) && (ppr->color == RED)) {//情况一:叔父节点为红色时,将上两极的颜色翻转,x换为pp
p->color = BLACK; //父节点设置为黑色
ppr->color = BLACK; //叔父节点设置为黑色
pp->color = RED; //父父节点设置为红色
x = pp; //x转为父父节点
} else {//叔父节点为空,或者为黑色
if(x == p->right) {//情况二:x与p的结构属性不同,按照p进行左旋
x = p;
rotate_left(root, x);//进行左旋
}
//情况三:按照祖父节点进行右旋
x->parent->color = BLACK; //父节点为黑色
x->parent->parent->color = RED;//父父节点为红色
rotate_right(root, x->parent->parent);//进行右旋
}
} else {//父节点是父父节点的右孩子
if((ppl != NULL) && (ppl->color == RED)) {//叔父节点为红色时,将上两极的颜色翻转
p->color = BLACK;//父节点设为黑色
ppl->color = BLACK;//叔父节点设为黑色
pp->color = RED;//父父节点设为红色
x = pp;//x换为父父节点
} else {//叔父节点为黑色
if(x == p->left) {//x是左孩子,需要进行右旋
x = p;
rotate_right(root, x);
}
x->parent->color = BLACK;//x的父节点为黑色
x->parent->parent->color = RED;//x的父父节点为红色
rotate_left(root, x->parent->parent);//x的父父节点左旋
}
}
}
(*root)->color = BLACK;//设置跟节点为黑色
}
static void * put(struct entry ** root, void * key, void * data)
{
struct entry * t = *root;
struct entry * tt = *root;
struct entry * r = NULL;
struct entry * x;
int cmp = 0;
if(NULL == t) {//根节点为空,创建一颗树
printf("is null!\n");
r = entry_new(key, data);
r->is_root = true;
r->color = BLACK;
r->mod_count++;
r->e_size++;
*root = r;
return NULL;
}
//这个比较函数没有通过函数的方式进行赋值,可以再根节点创建后进行赋值
if(NULL ==(*root)->cmp)
(*root)->cmp = default_cmp;
//如果键值已经存在,则重置键值
do {
x = t;
cmp = (*root)->cmp(key,x->key);
if(cmp > 0)
t = t->right;
else if (cmp < 0)
t = t->left;
else
return set_value(t, data);
} while(t != NULL);
//键不存在在新建节点
r = entry_new(key, data);
//r->root = *root;
if(cmp < 0)
x->left = r;
else
x->right = r;
r->parent = x;
tt->mod_count++;
r->e_size++;
fix_after_insertion(root, r);
return NULL;
}
因为红黑树是一颗有序树,树的左边总是小于树的右边。因此只要找到左节点,就是整颗树的最小值,找到整颗树的最右边,就是整棵树的最大值。
/*
* 寻找最左边的孩子,也就是整棵树的最小值的节点
*/
static struct entry * entry_node_first(struct entry * root)
{
struct entry * nl;
nl = root;
if(!nl)
return NULL;
while(nl->left)
nl = nl->left;
return nl;
}
/*
* 寻找最右边的孩子,也就是整棵树的最大值的节点
*/
static struct entry * entry_node_last(struct entry * root)
{
struct entry * nr;
nr = root;
if(!nr)
return NULL;
while(nr->right)
nr = nr->right;
return nr;
}
5.节点的删除
节点删除后,容易造成黑色节点的高度不同。因此节点删除时树结构调整的总原则是:如果被删除的节点是黑色,需要进行调整。
节点的删除有以下几种情况:被删除的节点记为D
(1)被删除节点没有孩子(也就是叶子节点),被删除的叶子节点是黑色时,进行树结构的调整,然后直接将该节点删除
(2)被删除节点只有一个孩子,那么直接删除该节点,并用该节点的唯一子节点替换它。如果替换的节点是黑色,进行树结构的调整
(3)被删除节点有两个孩子,先找到该节点的替换节点(被删除节点的左子树的最左节点),此时被删除的节点变为了替换节点(替换节点的键和值拷贝到被删除的节点,需要将替换的节点删除掉),如果替换节点为黑色,进行树结构的调整。
/*
* 找到删除节点的继承者
* 原则:当删除节点存在左和右节点的时候,需要寻找
且继承者是其右孩子或者右孩子的最左孩子
* 从删除函数中可知,找到的后继者肯定都是右子树的最左孩子
*/
static struct entry * successor(struct entry * x)
{
struct entry * p= NULL;
struct entry * ch = NULL;
if(NULL == x)
return NULL;
else if(x->right != NULL) {//1.x 的右子树不为空,则t的后继是其右子树中最小的那个元素
p = x->right;
while(p->left != NULL)
p = p->left;
return p;
} else {//2. x的右子树为空,则t的后继是其第一个向左走的祖先
p = x->parent;
ch = x;
while(p != NULL && ch == p->right) {
ch = p;
p = p->parent;
}
return p;
}
}
static void delete_entry(struct entry ** root, struct entry * p)
{
struct entry *s = NULL;
struct entry *replace = NULL;
struct entry * r = *root;
struct entry * node = *root;
r->e_size--; //树中节点的个数
r->mod_count++;//树的修改次数
printf("tree->key:%ld, tree->data:%ld, is_root:%d, size:%d, color:%d\n", (long)p->key, (long)p->data, p->is_root, (*root)->mod_count, p->color);
if((p->left != NULL) && (p->right != NULL)) {//(3).删除点p的左右子树都非空,这个情况会最终转换为删除叶子节点
s = successor(p);//继承者是右孩子的最左子孙
//使用继承者替换要删除的节点
p->key = s->key;
p->data = s->data;
//上面两步是找到继承者之后,替换key和value
p = s;//将继承者赋值为p,进行后续的处理
printf("tree->key:%ld, tree->data:%ld, is_root:%d, size:%d, color:%d\n", (long)p->key, (long)p->data, p->is_root, (*root)->mod_count, p->color);
}
//如果p有左孩子则取左孩子,否则取右孩子
replace = (p->left != NULL ? p->left : p->right);
if(replace != NULL) {//(2).删除点只有一个子树非空
replace->parent = p->parent;
if(p->parent == NULL) {//p节点没有父节点,则p是父节点
p->is_root = false;
replace->mod_count = p->mod_count;
replace->e_size = p->e_size;
if(p->cmp != NULL)
replace->cmp = p->cmp;
replace->is_root = true;
replace->color = BLACK;
*root = replace;
} else if(p = p->parent->left) {//删除p的父节点到p的引用
p->parent->left = replace;
} else
p->parent->right = replace;
p->left = p->right = p->parent = NULL;//解除p的左右节点以及父节点的引用
if(p->color == BLACK)
fix_after_deletion(root, replace);
} else if(p->parent == NULL) {//(4):如果一个节点没有左右节点以及父节点,则说明是根节点,也就是只有一个节点
*root = NULL;
} else {//(1)被删除的节点是叶子节点
if (p->color == BLACK)
fix_after_deletion(root, p); //根据
if(p->parent != NULL) {
if(p == p->parent->left)
p->parent->left = NULL;
else if(p == p->parent->right)
p->parent->right = NULL;
p->parent= NULL;
}
}
if(p)
free(p);
}
删除时的结构调整,有如下几种情况
1)兄弟节点是红色(说明父节点肯定黑色,且兄弟的孩子都是黑色)
2)兄弟节点是黑色,且兄弟节点的两个子节点都是黑色
3)兄弟节点是黑色,且兄弟节点的左子节点为红色,右子节点为黑色
4)兄弟节点是黑色,且兄弟节点的右子节点为红色,左子节点任意色
2)3)4)三种情况是兄弟节点为黑色时的子集。
情况一:当前节点是黑色,且兄弟节点是红色(此时父节点和子节点肯定是黑色的)
处理办法:将父节点和兄弟节点颜色翻转,并按照父节点进行旋转(旋转的方向与当前节点的结构属性相同(左孩子向左,右孩子向右)),调整之后,树变有了2)3)4)的属性特点了
情况二:兄弟节点是黑色,且兄弟节点的子节点都是黑色
处理办法:将兄弟节点颜色翻转,然后将当前节点设为父节点,开始新一轮的颜色检查
情况三:兄弟节点为黑色,它的左子节点为红色(或者不存在),它的右子节点为黑色。(此情况调整之后,必然要走情况四)
处理办法:A.将兄弟节点及其孩子的颜色翻转;B.按照兄弟节点进行旋转(旋转的方向与当前节点的结构属性相反(当前节点为左节点,则右旋,反之亦然))C.旋转后,从新设置兄弟节点
情况四:兄弟节点是黑色,兄弟孩子的右孩子(左孩子)是红色,左孩子任意
处理办法:
A.兄弟节点的颜色为父节点颜色
B.父节点以及右节点(或左节点)设置为黑色
c.按照父节点进行旋转(旋转方法与节点的结构属性相同(左节点往左旋,反之亦然))
d.当前节点设置为根节点(意味着结束调整)
从以上的特点可以看出,如果是情况一,那么可能出现2,3,4的情况。如果是情况二,那么需要进行新一轮的情况检查后并做相关的操作。如果是情况3,那么必然出现情况四,如果二和三都不是,那么必然是四。
static void fix_after_deletion(struct entry ** root, struct entry * x)
{
struct entry * node = NULL;
while(x && (x != *root) && (x->color == BLACK)) {
if(x == x->parent->left) { //x是左孩子
node = x->parent->right;
if(node && node->color == RED) {//如果右孩子是红色,此时父节点肯定是黑色
/*
* 删除节点是黑色,兄弟节点为红色
* 兄弟节点和父节点的颜色翻转
* 将父节点进行左旋
* 交换后x的父节点不变,但是其父节点的右孩子变为父节点原来右孩子的左孩子
*/
node->color = BLACK;
x->parent->color = RED;
rotate_left(root, x->parent);
node = x->parent->right;
}
if(node->left && node->left->color == BLACK && node->right && node->right->color == BLACK) {
/*
* 此时,x为黑,x的兄弟为黑,x的兄弟的黑子都是黑
* 将x的兄弟设为红色
* x转移到x的父节点
*/
node->color = RED;
x = x->parent;
} else {
if(node->right && node->right->color == BLACK) {
/*
* 此时左边为红色,右边为黑色
* 将左右两边的颜色翻转
* 右旋后
*/
if(node->left)
node->left->color = BLACK;
node->color = RED;
rotate_right(root, node);
node = x->parent->right;
}
node->color =x->parent->color;
x->parent->color = BLACK;
node->right->color = BLACK;
rotate_left(root, x->parent);
x = *root;
}
} else {
node = x->parent->left;
if(node && node->color == RED) {
node->color = BLACK;
x->parent->color = RED;
rotate_right(root, x->parent);
node = x->parent->left;
}
if(node->right && node->right->color == BLACK && node->left && node->left->color == BLACK) {
node->color = RED;
x = x->parent;
} else {
if(node->left && node->left->color == BLACK) {
if(node->right)
node->right->color = BLACK;
node->color = RED;
rotate_left(root, node);
node = node->parent->left;
}
node->color = x->parent->color;
x->parent->color = BLACK;
node->left->color = BLACK;
rotate_right(root, x->parent);
x = *root;
}
}
}
x->color = BLACK;
}
图截取于http://www.cnblogs.com/CarpenterLee/p/5525688.html
6.树的遍历
红黑树是二叉平衡的有序树,因此可以采用从根节点开始分为左边遍历以及右边遍历,也就是找到树的最小节点或者最大节点,然后根据下一节点进行遍历。
/*
* 查找左边的下一个
*/
static struct entry * entry_node_next(struct entry * root)
{
struct entry * parent;
struct entry * r = root;
if(!root->parent) {//如果是根节点,返回
return NULL;
}
if(r->right) {//
r = r->right;
while (r->left)
r = r->left;
return r;
}
while((parent = r->parent) && r == parent->right)//从最后一个叶子节点找到根节点
r = parent;
return parent;
}
/*
* 查找右边的下一个
*/
static struct entry * entry_node_prev(struct entry * root)
{
struct entry *parent;
struct entry * node = root;
if(!root->parent)
return NULL;
if(node->left) {
node = node->left;
while(node->right)
node = node->right;
return node;
}
while((parent = node->parent) && node == parent->left)
node = parent;
return parent;
}
遍历函数
static void print_the_tree(struct entry * root)
{
struct entry * node = NULL;
//左边的树
for(node = entry_node_first(root); node != root; node = entry_node_next(node)) {
printf("node->key:%ld, node->data:%ld, is_root:%d, color:%d\n", (long)node->key, (long)node->data, node->is_root, node->color);
}
//右边的树
for(node = entry_node_last(root); node != root; node = entry_node_prev(node)) {
printf("node->key:%ld, node->data:%ld, is_root:%d, color:%d\n", (long)node->key, (long)node->data, node->is_root, node->color);
}
printf("root->key:%ld, root->data:%ld, is_root:%d, size:%ld, color:%d\n", (long)root->key, (long)root->data, root->is_root, root->e_size, root->color);
}