1、红黑树简介
红黑树是二叉查找树的一种,其增删改查的统计性能要优于AVL树,查找、插入、删除算法的复杂度都为O(log(n))。先附上红黑树这种数据结构的性质:
性质1、节点是红色或黑色。
性质2、根节点是黑色。
性质3、每个叶节点(是指的空节点,nil节点)是黑色的。
性质4、每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质5、从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
红黑树不是完全平衡的二叉树,根据红黑树的性质4和性质5可以看出,从根到叶子节点的最长路径小于最短路径的两倍,同时也可以证明树的深度不超过2log(n)。但是插入和删除算法代价小,可以通过旋转和着色操作就可以使插入或删除后的树仍旧符合红黑树的性质。所以其查找、插入、删除算法的复杂度都为O(log(n))。
2、 左旋和右旋操作
红黑树的旋转操作有左旋和右旋,可以看出,不管是左旋还是右旋,旋转后的树仍然是一棵二叉查找树。
首先,左旋操作得保证左旋节点有右儿子。左旋的过程可以分为三个阶段,先把节点右儿子的做儿子变为节点的右儿子,再把节点的放到节点右儿子的左儿子的位置,最后将节点右儿子与节点的父节点相连。另外,如何此次旋转导致根节点改变,则应该更新根节点。如图所示:
右旋操作和左旋相类似,如图所示:
下面给出左旋和右旋的代码(c++):
/*******************************
struct Node{
int value;
char color; //red -> 'r' ----- black -> 'b'
Node *left_node;
Node *right_node;
Node *father_node;
Node(int v,char c,Node *l=NULL,Node *r=NULL,Node *f=NULL){
(*this).value=v;
(*this).color=c;
(*this).left_node=l;
(*this).right_node=r;
(*this).father_node=f;
}
};
struct Tree{
Node *root;
Tree(Node *r=NULL){
(*this).root=r;
}
};
*******************************/
void rotate_left(Tree *t,Node *x){
if(x->right_node==NULL) return;
Node *y=x->right_node;
Node *f=x->father_node;
//y->left_node ==> x->right_node
x->right_node=y->left_node;
if(y->left_node!=NULL)y->left_node->father_node=x;
//x ==> y->left_node
y->left_node=x;
x->father_node=y;
//x->father_node ==> y->father_node
y->father_node=f;
if(f==NULL) t->root=y;
else if(f->left_node==x) f->left_node=y;
else if(f->right_node==x) f->right_node=y;
}
void rotate_right(Tree *t,Node *x){
if(x->left_node==NULL) return;
Node *y=x->left_node;
Node *f=x->father_node;
//y->right_node ==> x->left_node
x->left_node=y->right_node;
if(y->right_node!=NULL)y->right_node->father_node=x;
//x ==> y->right_node
y->right_node=x;
x->father_node=y;
//x->father_node ==> y->father_node
y->father_node=f;
if(f==NULL) t->root=y;
else if(f->left_node==x) f->left_node=y;
else if(f->right_node==x) f->right_node=y;
}
3、添加节点操作
添加节点的操作首先应按照二叉查找树的性质添加一个红色节点(如果是根节点则为黑色),使得添加之后仍然是二叉查找树。
下面给出添加节点的代码(c++):
/*******************************
struct Node{
int value;
char color; //red -> 'r' ----- black -> 'b'
Node *left_node;
Node *right_node;
Node *father_node;
Node(int v,char c,Node *l=NULL,Node *r=NULL,Node *f=NULL){
(*this).value=v;
(*this).color=c;
(*this).left_node=l;
(*this).right_node=r;
(*this).father_node=f;
}
};
struct Tree{
Node *root;
Tree(Node *r=NULL){
(*this).root=r;
}
};
*******************************/
Node *insert(Tree *t, int n){
Node *r=t->root;
Node *y;
Node *ins=new Node(n,'r');
//if tree is empty, this node will be the root of the tree.
if(r==NULL){
ins->color='b'; //if this node is rootnode, the color will be black.
t->root=ins;
}else{
while(r!=NULL){
if(n<r->value){
y=r;
r=r->left_node;
}
else{
y=r;
r=r->right_node;
}
}
ins->father_node=y;
if(n<y->value){
y->left_node=ins;
}else{
y->right_node=ins;
}
}
return ins;
}
但是这样添加之后不一定符合红黑树的性质,所以我们要进行调整,使其重新符合红黑树的性质。(调整操作包括旋转和重新着色,这两种操作都不会改变二叉查找树的性质。)
在添加完成之后,如果其父节点是黑色,则仍然符合红黑树的性质,无需进行调整;如果其父节点为红色,调整步骤如下:
(1) 如果其叔叔节点也为红色,将当前节点的父节点和叔叔节点变为黑色,祖父节点变为红色(祖父节点未改变前一定是黑色,如果是红色则添加之前就不符合红黑树性质4,故不可能),然后将当前节点的祖父节点改为当前节点。循环操作直到当前节点的叔叔节点为黑色。
(2) 如果此时当前节点的父节点是祖父节点的左儿子,而当前节点是其父节点的右儿子,则令其父节点作为当前节点,进行左旋。
(3) 如果此时当前节点的父节点是祖父节点的左儿子,而当前节点是其父节点的左儿子,则令其父节点为黑色,祖父节点为红色,将当前节点的祖父节点改为当前节点,进行右旋。
注意:(2)和(3)中,如果此时当前节点的父节点是祖父节点的右儿子,则左右操作颠倒。
上面三种情况处理问题的核心思路都是:将红色的节点移到根节点,然后,将根节点设为黑色。经过调整,就可以重新变为红黑树了。
下面给出调整新插入节点的代码(c++):
/*******************************
struct Node{
int value;
char color; //red -> 'r' ----- black -> 'b'
Node *left_node;
Node *right_node;
Node *father_node;
Node(int v,char c,Node *l=NULL,Node *r=NULL,Node *f=NULL){
(*this).value=v;
(*this).color=c;
(*this).left_node=l;
(*this).right_node=r;
(*this).father_node=f;
}
};
struct Tree{
Node *root;
Tree(Node *r=NULL){
(*this).root=r;
}
};
*******************************/
void insert_fix(Tree *t,Node *x){
if(t->root==x) return;
else if(x->father_node->color=='b') return;
//x must have grandfather
Node *now=x;
while(now->father_node->color=='r'){
Node *u;
int isleft;
if(now->father_node->father_node->left_node==now->father_node){
u=now->father_node->father_node->right_node;
isleft=1;
}
else{
u=now->father_node->father_node->left_node;
isleft=0;
}
if(u!=NULL && u->color=='r'){
//father node and uncle node will be set to black
now->father_node->father_node->left_node->color='b';
now->father_node->father_node->right_node->color='b';
//if grandfather node isn't root node, it will be set to red.Then NOW node will be set to grandfather node.
//if grandfather node is root node, break.
if(now->father_node->father_node!=t->root){
now->father_node->father_node->color='r';
now=now->father_node->father_node;
continue;
}else{
break;
}
}else{
//if father node is the left son of grandfather node.
if(isleft){
//if now node is right son.
if(now->father_node->right_node==now){
now=now->father_node;
rotate_left(t,now);
}
now->father_node->color='b';
now->father_node->father_node->color='r';
now=now->father_node->father_node;
rotate_right(t,now);
}else{ //if father node is the right son of grandfather node.
//if now node is left son.
if(now->father_node->left_node==now){
now=now->father_node;
rotate_right(t,now);
}
now->father_node->color='b';
now->father_node->father_node->color='r';
now=now->father_node->father_node;
rotate_left(t,now);
}
}
}
}
4、 删除节点操作
删除操作首先应该查找节点是否存在于树中。
下面给出查找节点的代码(c++):
/*******************************
struct Node{
int value;
char color; //red -> 'r' ----- black -> 'b'
Node *left_node;
Node *right_node;
Node *father_node;
Node(int v,char c,Node *l=NULL,Node *r=NULL,Node *f=NULL){
(*this).value=v;
(*this).color=c;
(*this).left_node=l;
(*this).right_node=r;
(*this).father_node=f;
}
};
struct Tree{
Node *root;
Tree(Node *r=NULL){
(*this).root=r;
}
};
*******************************/
Node * find_node (Tree *t,int n){
Node *ret=NULL;
Node *x=t->root;
while(x!=NULL){
if(n==x->value){
ret=x;
break;
}
else if(n<x->value) x=x->left_node;
else x=x->right_node;
}
return ret;
}
然后将此树按照二叉查找树的性质删除节点。分为三种情况:
(1) 如果删除的节点没有左儿子或右儿子,则直接删除。
(2) 如果删除的节点只有一个儿子,则删除该节点,它的儿子代替他的位置
(3) 如果删除的节点有两个儿子,则需要找到它的后继节点(中序遍历后的集合某个元素的下一个元素),将后继节点的值拷贝到此节点(不改变颜色)。从二叉树的性质可以看出,此时后继节点不可能既有左儿子也有右儿子,然后执行删除后继节点的操作。(执行步骤(1)或(2))。
下面给出按照二叉查找树的性质删除节点的代码(c++):
/*******************************
struct Node{
int value;
char color; //red -> 'r' ----- black -> 'b'
Node *left_node;
Node *right_node;
Node *father_node;
Node(int v,char c,Node *l=NULL,Node *r=NULL,Node *f=NULL){
(*this).value=v;
(*this).color=c;
(*this).left_node=l;
(*this).right_node=r;
(*this).father_node=f;
}
};
struct Tree{
Node *root;
Tree(Node *r=NULL){
(*this).root=r;
}
};
*******************************/
void delete_node(Tree *t,Node *x){
if(x==NULL) return;
Node *l=x;
if(x->left_node!=NULL && x->right_node!=NULL){
l=x->right_node;
//find Late node point
while(l->left_node!=NULL) l=l->left_node;
x->value=l->value;
}
if(l->left_node!=NULL && l->right_node==NULL){//the node have left son but havn't right son.
if(l->father_node!=NULL){//the node isn't root node.
l->left_node->father_node=l->father_node;
if(l->father_node->left_node==l) l->father_node->left_node=l->left_node;
else l->father_node->right_node=l->left_node;
}else{
l->left_node->father_node=NULL;
t->root=l->left_node;
t->root->color='b';//the color of root node is black.
}
//fix the tree.
if(l->color=='b' && l->left_node!=t->root) delete_fix(t,l->left_node);
}else if(l->left_node==NULL && l->right_node!=NULL){//the node have right son but havn't left son.
if(l->father_node!=NULL){
l->right_node->father_node=l->father_node;
if(l->father_node->left_node==l) l->father_node->left_node=l->right_node;
else l->father_node->right_node=l->right_node;
}else{
l->right_node->father_node=NULL;
t->root=l->right_node;
t->root->color='b';
}
//fix the tree.
if(l->color=='b' && l->right_node!=t->root) delete_fix(t,l->right_node);
}else{//the node neither have left son nor have right son.
if(l->father_node!=NULL){
int nil;
if(l->father_node->left_node==l){
l->father_node->left_node=NULL;
nil=1;
}
else{
l->father_node->right_node=NULL;
nil=2;
}
//fix the tree.
if(l->color=='b') delete_fix(t,l,nil);
}else{
t->root=NULL;
}
}
delete l;
}
删除节点后,需要保证删除后仍然是一棵红黑树,需要对树进行调整。首先,如果删除的节点为红色节点,那么删除后仍然满足红黑树的性质,不用调整;如果删除的节点为黑色节点,但是是根节点,将后来代替的根节点染黑就可以了。如果删除的节点为黑色节点且不是根节点,调整步骤如下(假设被删除的点的位置(当前节点)是其父节点的左儿子):
(1) 如果当前节点是黑色,它的兄弟节点为红色,将此兄弟节点变为黑色,父节点变为红色,然后在父节点上进行左旋,左旋后更新当前节点的兄弟节点(因为左旋会使当前节点的兄弟节点改变)。
(2) 如果当前节点是黑色,它的兄弟节点也是黑色,而且兄弟节点的两个儿子都为黑色,将兄弟节点变为红色。然后更新当前节点为其父节点,因为当前节点向上更新,所以如果父节点为红,将它变为黑色即可弥补删除的黑色节点,使得树重新符合红黑树的性质。
(3) 如果当前节点是黑色,它的兄弟节点也是黑色,而且兄弟节点的左儿子为红色,右儿子为黑色。将左儿子变成黑色,兄弟节点变成红色,在兄弟节点上进行右旋,右旋后更新当前节点的兄弟节点。
(4) 如果当前节点是黑色,它的兄弟节点也是黑色,而且兄弟节点的右儿子为红色(左儿子任意)。将父节点的颜色赋值给兄弟节点,再将父节点设置为黑色,再将兄弟节点的右儿子设置为黑色,在父节点上左旋。这些操作后即可重新符合红黑树的性质。
注意:如果此时当前节点是父节点的右儿子,则左右操作颠倒。还有应该将nil节点作为真正的节点加入树中,而不是将nil节点直接NULL。这样会减少编程上的麻烦。(我没使用nil节点,发现编程过程中会有些麻烦,可读性不是很好。)
删除调整算法的思想是将当前节点不断向根方向移动,也就是说将删除的黑色逐渐向上传递,直到出现当前节点指向一个红色节点。此时,将当前节点设为一个"黑"节点即可;或当前节点指向根,因为根不影响红黑树的性质。
下面给出删除节点后调整的代码(c++):
/*******************************
struct Node{
int value;
char color; //red -> 'r' ----- black -> 'b'
Node *left_node;
Node *right_node;
Node *father_node;
Node(int v,char c,Node *l=NULL,Node *r=NULL,Node *f=NULL){
(*this).value=v;
(*this).color=c;
(*this).left_node=l;
(*this).right_node=r;
(*this).father_node=f;
}
};
struct Tree{
Node *root;
Tree(Node *r=NULL){
(*this).root=r;
}
};
*******************************/
void delete_fix(Tree *t,Node *x,int nil=0){
if(t->root==NULL) return;
if(x->color=='r'){//when change now node up, if the father node is red, set to black, then finish.
x->color='b';
return;
}
Node *now=x;
while(now->color=='b' && now!=t->root){
if(nil==1 || now->father_node->left_node==now){//if node now is the left son.
Node *f=now->father_node;
Node *b=f->right_node;
if(b!=NULL && b->color=='r'){
b->color='b';
f->color='r';
rotate_left(t,f);
b=f->right_node;
}
if((b!=NULL && b->color=='b')){
if((b->left_node==NULL||b->left_node->color=='b')&&((b->right_node==NULL||b->right_node->color=='b'))){
b->color='r';
now=f;
if(now->color=='r'){//when change now node up, if the father node is red, set to black, then finish.
now->color='b';
return;
}else{
continue;
}
}else{
//the left son of b node is red and the right son of b node is black.
if((b->left_node!=NULL&&b->left_node->color=='r')&&((b->right_node==NULL||b->right_node->color=='b'))){
b->left_node->color='b';
b->color='r';
rotate_right(t,b);
b=f->right_node;
}
//the right son of b node is red.
if(b->right_node!=NULL && b->right_node->color=='r'){
b->color=f->color;
f->color='b';
if(b->right_node!=NULL) b->right_node->color='b';
rotate_left(t,f);
return;
}
}
}
}else if(nil==2 || now->father_node->right_node==now){//if node now is the right son.
Node *f=now->father_node;
Node *b=f->left_node;
if(b!=NULL && b->color=='r'){
b->color='b';
f->color='r';
rotate_right(t,f);
b=f->left_node;
}
if(b!=NULL && b->color=='b'){
if((b->right_node==NULL||b->right_node->color=='b')&&((b->left_node==NULL||b->left_node->color=='b'))){
b->color='r';
now=f;
if(now->color=='r'){//when change now node up, if the father node is red, set to black, then finish.
now->color='b';
return;
}else{
continue;
}
}else{
//the right son of b node is red and the left son of b node is black.
if((b->right_node!=NULL&&b->right_node->color=='r')&&((b->left_node==NULL||b->left_node->color=='b'))){
b->right_node->color='b';
b->color='r';
rotate_left(t,b);
b=f->left_node;
}
//the left son of b node is red.
if(b->left_node!=NULL && b->left_node->color=='r'){
b->color=f->color;
f->color='b';
b->left_node->color='b';
rotate_right(t,f);
return;
//if(t->root!=now) t->root=now;
}
}
}
}
}
}