标签:
版权声明:原创不易,转载请注明转自weewqrer 红黑树
首先红黑树是一棵二叉搜索树,它在每个结点上增加了一个存储位来表示结点的颜色,可以是RED或者BLACK。通过对一条从根节点到NIL叶节点(指空结点或者下面说的哨兵)的简单路径上各个结点在颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因而是近似平衡的。
红黑树和AVL树一样都对插入时间、删除时间和查找时间提供了最好可能的最坏情况担保。对于查找、插入、删除、最大、最小等动态操作的时间复杂度为O(lgn).常见的用途有以下几种:
常见的平衡树有红黑树和AVL平衡树,为什么STL和linux都使用红黑树作为平衡树的实现?大概有以下几个原因:
从实现细节上来讲,如果插入一个结点引起了树的不平衡,AVL树和红黑树都最多需要2次旋转操作,即两者都是O(1);但是在删除node引起树的不平衡时,最坏情况下,AVL需要维护从被删node到root这条路径上所有node的平衡性,因此需要旋转的量级O(logN),而RB-Tree最多只需3次旋转,只需要O(1)的复杂度
从两种平衡树对平衡的要求来讲,AVL的结构相较RB-Tree来说更为平衡,在插入和删除node更容易引起Tree的unbalance,因此在大量数据需要插入或者删除时,AVL需要rebalance的频率会更高。因此,RB-Tree在需要大量插入和删除node的场景下,效率更高。自然,由于AVL高度平衡,因此AVL的search效率更高。
总体来说,RB-tree的统计性能是高于AVL的。
《算法导论》和《数据结构与算法分析》是大家常用的两本算法书,针对红黑树这一章,这两本书上也都有,但是二者从数据结构到使用的方法上都不一样,这里我推荐使用《算法导论》。有以下几个原因:
红黑树是每个结点都带有颜色属性的二叉查找树,颜色为红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:
typedef enum ColorType {RED, BLACK} ColorType;
typedef struct rbt_t{
int key;
rbt_t * left;
rbt_t * right;
rbt_t * p;
ColorType color;
}rbt_t;
typedef struct rbt_root_t{
rbt_t* root;
rbt_t* nil;
}rbt_root_t;
/*
*@brief rbt_init 初始化
*/
rbt_root_t* rbt_init(void){
rbt_root_t* T;
T = (rbt_root_t*)malloc(sizeof(rbt_root_t));
assert( NULL != T);
//用一个哨兵代表NIL。
T->nil = (rbt_t*)malloc(sizeof(rbt_t));
assert(NULL != T->nil);
T->nil->color = BLACK;
T->nil->left = T->nil->right = NULL;
T->nil->p = NULL;
T->root = T->nil;
return T;
}
搜索树操作inert和delete在含有n个关键字的红黑树上,运行花费时间为
指针结构的修改是通过旋转来完成的,这是一种能保持二叉搜索树性质的局部操作。一种有两种旋转操作,如下图所示,都在O(1)时间内完成。
这里要求x,y都不是T.nil。
c代码:
/*
*@brief rbt_left_rotate
*@param[in] T 树根
*@param[in] x 要进行旋转的节点
*/
void rbt_left_rotate( rbt_root_t* T, rbt_t* x){
rbt_t* y = x->right;
x->right = y->left;
if(x->right != T->nil)//更新某结点的父亲时,要确定此结点不是T.nil
x->right->p = x;
y->p = x->p;
if(x->p == T->nil){//如果x以前是树根,那么现在树根易主了
T->root = y;
}else if(y->key < y->p->key)
y->p->left = y;
else
y->p->right = y;
y->left = x;
x->p = y;
}
/*
*@brief rbt_right_rotate
*@param[in] 树根
*@param[in] 要进行旋转的节点
*/
void rbt_right_rotate( rbt_root_t* T, rbt_t* x){
rbt_t * y = x->left;
x->left = y->right;
if(T->nil != x->left)
x->left->p = x;
y->p = x->p;
if(y->p == T->nil)
T->root = y;
else if(y->key < y->p->key)
y->p->left= y;
else
y->p->right = y;
y->right = x;
x->p = y;
}
在红黑树中插入一个元素,跟在二叉搜索树中插入一个元素一样,只是插入一个元素之后,有可能使得这个树不再平衡,所以要再处理一下,使之重新回到平衡状态。
图中N为新插入的结点,U为它的叔叔。
插入操作的关键在于以下几点:
插入一个红色节点要处理这么几种情况:
此时要记住一件事事情,插入时总是要考虑它的叔叔,删除时总要考虑它的兄弟。而且插入时维护的主要是颜色(性质4),而删除时维护的主要是黑色结点数量(性质5)
情况1:
N为红,P为红(GP一定为黑),U为红。
下面会说明我们可以通过一种特殊的处理把这种情况避免掉。
那为什么要避免这种情况呢?因为这种情况一般是通过颜色翻转来处理的,也就是把P U换成黑色,把GP抱成红色,但是GP的父亲如果是红色的话又会违反红黑树的性质。
情况2:
N,P都为红(GP一定为黑),U为黑
根据境像,情况2可细分为4种情况,如下:
但是这四种具体情况的处理手法是一样的,都是通过颜色翻转与旋转来处理的。下面我们通过情况2.1和2.2来说明一下处理方法:
情况2.2通过调用left_rotate(T,p)变成情况2.1;
情况2.1通过交换GP与P的颜色,然后调用right_rotate(T,GP),此时不再违反任何性质。
情况2.3和2.4分别是2.1和2.2的境像。
如何避免情况1
令X = T.root,在向下遍历的过程中,我们如果遇到X.right.color == x.left.color == RED时我们将x与它孩子的颜色翻转,即把x涂成红色,把x.right和x.left涂成黑色。
如果x的父亲为黑色,没有违反性质;如果x的父亲为红色,那么可以把x当成新插入的红色结点N,那么只需要处理情况2即可。
至此,插入完成,具体实现可以看完整代码部分,代码也有必要的注释。
还是上面同样那句话,插入时总是要考虑它的叔叔,删除时总要考虑它的兄弟。而且插入时维护的主要是颜色(性质4),而删除时维护的主要是黑色结点数量(性质5)。
写删除的代码花费了我大概一天多的时间,因为我总是试图找出一种比《数据结构与算法分析》上更清晰,比《算法导论》中更简单的方法,但是失败了(⊙▽⊙).
实际上删除操作也没有那么难,如果要删除 z 结点,那么就让 z 的后继来代替 z 的位置即可。 如果z是红色的,那么操作便完成了,删除一个红色结点没有违反任何性质。但如果z是黑色的,那么我们删除一个黑色结点,便违反了性质5,造成黑色结点数量的左右不平衡。只要分析出删除一个黑色结点会遇到哪些情况即可。
首先找到要删除的结点,我们定义它为 z 。
如果 z 的两个孩子都不是T.nil,那么我们在 z 的右子树中找出最小的结点 m,把 m 结点的值赋给 z (而不是把m移植到z的位置,也就不用考虑颜色问题,这一点是比《算法导论》中要简单的),那么我们要删除的结点就成为 m 了。m 肯定没有左孩子。令 z 重新指向 m .
找到要删除的结点 z 之后,我们用 z 的孩子(记作 x )来取代 z的位置(即使z.right == T.nil) 。rbt_transplant(T,z,z.right);
此时用到下面一段代码,实现用v代替u
void rbt_transplant(rbt_root_t* T, rbt_t* u, rbt_t* v){
if(u->p == T->nil)
T->root = v;
else if(u == u->p->left)
u->p->left =v;
else
u->p->right = v;
v->p = u->p;//即使v是T.nil也可以执行这一行
}
到目前为止,如果要被删除的 z 结点是红色的,那么程序就结束了。但是如果 z 是黑色的,所以删除z之后z这边少了一个黑色结点,会违反性质5,此时分为4种情况(x 是左孩子 和 x 是右孩子分别有4种情况,现在只讨论x是左孩子的情况):
情况1
x的兄弟w是红色的,那么它们的父亲、w的孩子都是黑色的。
这种情况下只能做一种无损的操作,通过交换颜色再旋转,对树的性质不会产生影响,所以从根到x结点的路径上少的一个黑色结点也不会补上。
交换p与w的颜色,再对p进行左旋操之后,x的新兄弟就为黑色,情况变成了2 3 4中的一种.
图中x为白色,表示我们不关心x的颜色。
情况2
x的兄弟w是黑色,而且w的两个孩子都是黑色。
此时可以细分为2种情况,但无论哪种情况,我们要进行的操作都是一样的,都是将w涂成红色,将p涂成黑色。
如果是情况2.1(有可能由情况1发展过来的),由于上述操作为x那边补上了一个黑色(从根到x在路径上多了一个黑色结点),此时红黑树性质5得到满足,程序结束。
如果是情况2.2, 经过上述操作后,P的右子树也少了一个黑色结点,令P作为新的X继续循环。
情况3
W是黑色有,w在左孩子是红色的,W的右孩子是黑色的。
通过交换L与W的颜色,再对W进行右旋操作。这种操作也不会对红黑树性质产生影响,此时进入情况4,我们会看到通过情况4中的操作最终使红黑树性质得到满足,结束程序。
图中最后边的R结点没有画出来,因为我们不关心它了
情况4
w是黑色的,w的右孩子是红色的。
把w涂成p的颜色,把P涂成黑色,R涂成黑色,左旋P。此时从根到x在路径上多了一个黑色结点,程序结束。
具体实现代码见下面。
#include<stdafx.h>
#include<malloc.h>
#include <assert.h>
//版权声明:原创不易,转载请注明转自[weewqrer 红黑树](http://blog.csdn.net/weewqrer/article/details/51866488)
//红黑树
typedef enum ColorType {RED, BLACK} ColorType;
typedef struct rbt_t{
int key;
rbt_t * left;
rbt_t * right;
rbt_t * p;
ColorType color;
}rbt_t;
typedef struct rbt_root_t{
rbt_t* root;
rbt_t* nil;
}rbt_root_t;
//函数声明
rbt_root_t* rbt_init(void);
static void rbt_handleReorient(rbt_root_t* T, rbt_t* x, int k);
rbt_root_t* rbt_insert(rbt_root_t* &T, int k);
rbt_root_t* rbt_delete(rbt_root_t* &T, int k);
void rbt_transplant(rbt_root_t* T, rbt_t* u, rbt_t* v);
static void rbt_left_rotate( rbt_root_t* T, rbt_t* x);
static void rbt_right_rotate( rbt_root_t* T, rbt_t* x);
void rbt_inPrint(const rbt_root_t* T, rbt_t* t);
void rbt_prePrint(const rbt_t * T, rbt_t* t);
void rbt_print(const rbt_root_t* T);
static rbt_t* rbt_findMin(rbt_root_t * T, rbt_t* t);
static rbt_t* rbt_findMax(rbt_root_t * T, rbt_t* t);
static rbt_t* rbt_findMin(rbt_root_t * T, rbt_t* t){
if(t == T->nil) return T->nil;
while(t->left != T->nil)
t = t->left;
return t;
}
static rbt_t* rbt_findMax(rbt_root_t * T, rbt_t* t){
if(t == T->nil) return T->nil;
while(t->right != T->nil)
t = t->right;
return t;
}
/*
*@brief rbt_init 初始化
*/
rbt_root_t* rbt_init(void){
rbt_root_t* T;
T = (rbt_root_t*)malloc(sizeof(rbt_root_t));
assert( NULL != T);
T->nil = (rbt_t*)malloc(sizeof(rbt_t));
assert(NULL != T->nil);
T->nil->color = BLACK;
T->nil->left = T->nil->right = NULL;
T->nil->p = NULL;
T->root = T->nil;
return T;
}
/*
*@brief rbt_handleReorient 内部函数 由rbt_insert调用
* 在两种情况下调用这个函数:
* 1 x有连个红色儿子
* 2 x为新插入的结点
*
*/
void rbt_handleReorient(rbt_root_t* T, rbt_t* x, int k){
//在第一种情况下,进行颜色翻转; 在第二种情况下,相当于对新插入的x点初始化
x->color = RED;
x->left->color = x->right->color = BLACK;
//如果x.p为红色,那么x.p一定不是根,x.p.p一定不是T.nil,而且为黑色
if( RED == x->p->color){
x->p->p->color = RED;//此时x, p, x.p.p都为红
if(x->p->key < x->p->p->key){
if(k > x->p->key){
x->color = BLACK;//小心地处理颜色
rbt_left_rotate(T,x->p);
rbt_right_rotate(T,x->p);
}else{
x->p->color = BLACK;//小心地处理颜色
rbt_right_rotate(T,x->p->p);
}
}else{
if(k < x->p->key){
x->color = BLACK;
rbt_right_rotate(T,x->p);
rbt_left_rotate(T,x->p);
}else{
x->p->color = BLACK;
rbt_left_rotate(T,x->p->p);
}
}
}
T->root->color = BLACK;//无条件令根为黑色
}
/*
*@brief brt_insert 插入
*1 新插入的结点一定是红色的,如果是黑色的,会破坏条件4(每个结点到null叶结点的每条路径有同样数目的黑色结点)
*2 如果新插入的结点的父亲是黑色的,那么插入完成。 如果父亲是红色的,那么做一个旋转即可。(前提是叔叔是黑色的)
*3 我们这个插入要保证其叔叔是黑色的。也就是在x下沉过程中,不允许存在两个红色结点肩并肩。
*/
rbt_root_t* rbt_insert(rbt_root_t* &T, int k){
rbt_t * x, *p;
x = T->root;
p = x;
//令x下沉到叶子上,而且保证一路上不会有同时为红色的兄弟
while( x != T->nil){
//
//保证没有一对兄弟同时为红色, 为什么要这么做?
if(x != T->nil)
if(x->left->color == RED && x->right->color == RED)
rbt_handleReorient(T,x,k);
p = x;
if(k<x->key)
x = x->left;
else if(k>x->key)
x = x->right;
else{
printf("\n%d已存在\n",k);
return T;
}
}
//为x分配空间,并对其进行初始化
x = (rbt_t *)malloc(sizeof(rbt_t));
assert(NULL != x);
x->key = k;
x->color = RED;
x->left = x->right = T->nil;
x->p = p;
//让x的父亲指向x
if(T->root == T->nil)
T->root = x;
else if(k < p->key)
p->left = x;
else
p->right = x;
//因为一路下来,如果x的父亲是红色,那么x的叔叔肯定不是红色了,这个时候只需要做一下翻转即可。
rbt_handleReorient(T,x,k);
return T;
}
void rbt_transplant(rbt_root_t* T, rbt_t* u, rbt_t* v){
if(u->p == T->nil)
T->root = v;
else if(u == u->p->left)
u->p->left =v;
else
u->p->right = v;
v->p = u->p;
}
/*
*@brief rbt_delete 从树中删除 k
*
*
*/
rbt_root_t* rbt_delete(rbt_root_t* &T, int k){
assert(T != NULL);
if(NULL == T->root) return T;
//找到要被删除的叶子结点
rbt_t * toDelete = T->root;
rbt_t * x;
//找到值为k的结点
while(toDelete != T->nil && toDelete->key != k){
if(k<toDelete->key)
toDelete = toDelete->left;
else if(k>toDelete->key)
toDelete = toDelete->right;
}
if(toDelete == T->nil){
printf("\n%d 不存在\n",k);
return T;
}
//如果两个孩子,就找到右子树中最小的代替, alternative最多有一个右孩子
if(toDelete->left != T->nil && toDelete->right != T->nil){
rbt_t* alternative = rbt_findMin(T, toDelete->right);
k = toDelete->key = alternative->key;
toDelete = alternative;
}
if(toDelete->left == T->nil){
x = toDelete->right;
rbt_transplant(T,toDelete,toDelete->right);
}else if(toDelete->right == T->nil){
x = toDelete->left;
rbt_transplant(T,toDelete,toDelete->left);
}
if(toDelete->color == BLACK){
//x不是todelete,而是用于代替x的那个
//如果x颜色为红色的,把x涂成黑色即可, 否则 从根到x处少了一个黑色结点,导致不平衡
while(x != T->root && x->color == BLACK){
if(x == x->p->left){
rbt_t* w = x->p->right;
//情况1 x的兄弟是红色的,通过
if(RED == w->color){
w->color = BLACK;
w->p->color = RED;
rbt_left_rotate(T,x->p);
w = x->p->right;
}//处理完情况1之后,w.color== BLACK , 情况就变成2 3 4 了
//情况2 x的兄弟是黑色的,并且其儿子都是黑色的。
if(w->left->color == BLACK && w->right->color == BLACK){
if(x->p->color == RED){
x->p->color = BLACK;
w->color = RED;
break;
}else{
w->color = RED;
x = x->p;//x.p左右是平衡的,但是x.p处少了一个黑结点,所以把x.p作为新的x继续循环
continue;
}
}
//情况3 w为黑色的,左孩子为红色。(走到这一步,说明w左右不同时为黑色。)
if(w->right->color == BLACK){
w->left->color = BLACK;
w->color = RED;
rbt_right_rotate(T,w);
w = x->p->right;
}//处理完之后,变成情况4
//情况4 走到这一步说明w为黑色, w的左孩子为黑色, 右孩子为红色。
w->color=x->p->color;
x->p->color=BLACK;
w->right->color=BLACK;
rbt_left_rotate(T,x->p);
x = T->root;
}else{
rbt_t* w = x->p->left;
//1
if(w->color == RED){
w->color = BLACK;
x->p->color = RED;
rbt_right_rotate(T,x->p);
w = x->p->left;
}
//2
if(w->left->color==BLACK && w->right->color == BLACK){
if(x->p->color == RED){
x->p->color = BLACK;
w->color = RED;
break;
}else{
x->p->color = BLACK;
w->color = RED;
x = x->p;
continue;
}
}
//3
if(w->left->color == BLACK){
w->color = RED;
w->right->color = BLACK;
w = x->p->left;
}
//4
w->color=w->p->color;
x->p->color = BLACK;
w->left->color = BLACK;
rbt_right_rotate(T,x->p);
x = T->root;
}
}
x->color = BLACK;
}
//放心删除todelete 吧
free(toDelete);
return T;
}
/*
*@brief rbt_left_rotate
*@param[in] T 树根
*@param[in] x 要进行旋转的结点
*/
void rbt_left_rotate( rbt_root_t* T, rbt_t* x){
rbt_t* y = x->right;
x->right = y->left;
if(x->right != T->nil)
x->right->p = x;
y->p = x->p;
if(y->p == T->nil){
T->root = y;
}else if(y->key < y->p->key)
y->p->left = y;
else
y->p->right = y;
y->left = x;
x->p = y;
}
/*
*@brief rbt_right_rotate
*@param[in] 树根
*@param[in] 要进行旋转的结点
*/
void rbt_right_rotate( rbt_root_t* T, rbt_t* x){
rbt_t * y = x->left;
x->left = y->right;
if(T->nil != x->left)
x->left->p = x;
y->p = x->p;
if(y->p == T->nil)
T->root = y;
else if(y->key < y->p->key)
y->p->left= y;
else
y->p->right = y;
y->right = x;
x->p = y;
}
void rbt_prePrint(const rbt_root_t* T, rbt_t* t){
if(T->nil == t)return ;
if(t->color == RED)
printf("%3dR",t->key);
else
printf("%3dB",t->key);
rbt_prePrint(T,t->left);
rbt_prePrint(T,t->right);
}
void rbt_inPrint(const rbt_root_t* T, rbt_t* t){
if(T->nil == t)return ;
rbt_inPrint(T,t->left);
if(t->color == RED)
printf("%3dR",t->key);
else
printf("%3dB",t->key);
rbt_inPrint(T,t->right);
}
//打印程序包括前序遍历和中序遍历两个,因为它俩可以唯一确定一棵二叉树
void rbt_print(const rbt_root_t* T){
assert(T!=NULL);
printf("\n前序遍历 :");
rbt_prePrint(T,T->root);
printf("\n中序遍历 :");
rbt_inPrint(T,T->root);
printf("\n");
}
void rbt_test(){
rbt_root_t* T = rbt_init();
/************************************************************************/
/* 1 测试插入
/*
/*
/*输出 前序遍历 : 7B 2R 1B 5B 4R 11R 8B 14B 15R
/* 中序遍历 : 1B 2R 4R 5B 7B 8B 11R 14B 15R
/************************************************************************/
T = rbt_insert(T,11);
T = rbt_insert(T,7);
T = rbt_insert(T,1);
T = rbt_insert(T,2);
T = rbt_insert(T,8);
T = rbt_insert(T,14);
T = rbt_insert(T,15);
T = rbt_insert(T,5);
T = rbt_insert(T,4);
T = rbt_insert(T,4); //重复插入测试
rbt_print(T);
/************************************************************************/
/* 2 测试删除
/*
/*操作 连续删除4个元素 rbt_delete(T,8);rbt_delete(T,14);rbt_delete(T,7);rbt_delete(T,11);
/*输出 前序遍历 : 2B 1B 5R 4B 15B
/* 中序遍历 : 1B 2B 4B 5R 15B
/************************************************************************/
rbt_delete(T,8);
rbt_delete(T,14);rbt_delete(T,7);rbt_delete(T,11);
rbt_delete(T,8);//删除不存在的元素
rbt_print(T);
}
代码只做了少量的测试,如果有BUG请不吝指出。
版权声明:原创不易,转载请注明转自weewqrer 红黑树
标签:
原文地址:http://blog.csdn.net/weewqrer/article/details/51866488