算法导论--红黑树

来源:互联网 发布:网络用语lg是什么意思 编辑:程序博客网 时间:2024/06/06 02:11

红黑树的基础是二叉搜索树,如对二叉搜索树的操作不了解,请参考上一篇文章二叉搜索树

红黑树操作有点复杂,请参照相关书籍,耐心研究......奋斗

部分图片参考博客:http://www.cnblogs.com/Anker/archive/2013/01/30/2882773.html


1.红黑树定义: 

  红黑树是平衡搜索树的一种,可以保证在最坏情况下基本动态集合操作的时间复杂度为Lgn。红黑树在二叉搜索树的基础上结点增加了一个属性color,值为black或者red,即一个节点的属性有:left,right,p,color,key。同时,红黑树使用一个哨兵T_NIL代替以一般搜索树中的NULL,T_NIL也是一个普通的结点,也具有以上5个属性,它的color属性为black,其他属性为任意值。即叶子结点的左右孩子全部指向哨兵,以及根节点的父指针也指向哨兵。

一颗红黑树满足以下性质:

1.每个根节点是红色或黑色;

2.根节点是黑色的;

3.每个叶子结点(哨兵)是黑色的;

4.如果一个节点是红色的,则它的两个子节点都是黑色的。

5.对每个结点出发,到达该结点后代叶节点的路径上均包含相同数目的黑色结点。

   黑高的定义:从某个结点出发(不包含该结点)到达一个叶节点的任意一条简单路径上的黑色结点个数称为该结点的黑高。一颗红黑树的黑高也就是根节点的黑高;

红黑树例子:

叶节点与根节点的父结点省略后:

   T_NIL=(Node *)malloc(sizeof(Node));  //建立T_NIL结点   T_NIL->p=NULL;   T_NIL->left=NULL;   T_NIL->right=NULL;   T_NIL->key=-1;   T_NIL->color=black;   //只用它的颜色为黑色,其他属性可以为任意值

2.旋转操作

  介绍插入与删除操作前,必须要了解旋转操作,因为插入和删除操作可能会违反红黑树的性质。旋转分为左旋和右旋,旋转是针对某一个结点做的,而且经过旋转操作后仍然能保持二叉搜索树的性质。



左旋与右旋代码,注意以后代码中不存在NULL,只有T_NIL,因为叶子节点左右节点全部指向哨兵节点T_NIL:

void Left_Rotate(Node **T,Node * x)   //左旋      对照上图很容易分析代码{   Node *y=x->right;     x->right =y->left;             //y的左孩子 作为x的右孩子   if (y->left != T_NIL)                    y->left->p=x;   y->p=x->p;                     // y父节点指向x的父节点   if(x->p==T_NIL)               //若x之前为根节点,则根节点更新      *T=y;   else if (x == x->p->left)     //若x之前为父节点的左孩子,则左孩子现在是y      x->p->left = y;   else  x->p->right = y;   y->left = x;   x->p=y;}void Right_Rotate(Node **T,Node * y)   //右旋{Node *x=y->left;y->left =x->right;           //x 的右孩子 作为 y的左孩子if (x->right != T_NIL)x->right->p=y;x->p=y->p;if(y->p==T_NIL)      // 若y之前为根节点,根节点更新                    *T=x;else if (y == y->p->left)  //若y为其父节点的左孩子,则左孩子现在是xy->p->left = x;else  y->p->right = x;x->right = y;y->p=x;}

3.插入操作

RB_Insert(T,z)是个二叉搜索树Tree_Insert基础上做的修改,新插入的结点令其颜色为红色,原因是这样不会破坏结点的黑高,若把z插入后,如果z的父节点也为红色,就破坏了性质4必须做修复RB_Insert_Fixup(T,z)。
先利用二叉搜索树的插入方法,把结点z插入进去,然后判断有没有违反红黑的性质。
z插入后有以下几种情况:
◆:z作为根节点:(破坏了性质2)修改根节点为黑色即可
◆:z的父节点为黑色: 不做操作
◆:z的父节点为红色:(破坏了性质4)
1.z的叔结点y是红色
2.z的叔结点y是黑色且z是一个右孩子
3.z的叔结点y是黑色且z是一个左孩子

依次讨论:
◆情况1:z的叔结点y是红色
不管父节点z->p是其父节点的左孩子还是右孩子,因为z的父节点为红色,那z->p->p->color必为黑色,即祖父结点必为黑色,这时我们把叔结点y和父节点z->p变为黑色,这样可以解决z和z->p都是红色的问题,把z->p->p变成红色,以保持性质5.这时我们只要考虑z->p->p以上的结点情况就行了,把z->p->p作为新结点z,再次循环判断。
由以上分析情况1不会违反其他红黑性质,但新结点z可能是根节点,结束循环违反了性质2,但是我们会在循环结束再次令根节点为黑色即可。(见代码)

◆情况2:z的叔结点y是黑色且z是一个右孩子
◆情况3:z的叔结点y是黑色且z是一个左孩子
情况2需要转到情况3处理
假如z的父节点为其祖父结点的左孩子:
   如图:假如从结点c到其α,β,γ,δ子树中叶子结点具有相同的黑色结点数k。情况2结点z是父节点的右孩子,对z->p执行一次左旋,并不会破坏红黑树的其他性质,因为都是红色结点,这样就转到了情况3,对z->p及z->p->p重新着色后即结点B变成黑色,结点C变成红色,然后对结点C做个右旋操作,如下图 而且此时从结点B到α,β,γ,δ子树中叶子结点数还是k,即保证了性质5,同时修正了性质4,中间并没有破坏任何其他性质,结束循环判断。

Node *RB_Insert(Node *Root,Node * z)   //红黑树插入,返回树的根,Root为树根,z为将要插入的结点{Node * y=T_NIL;Node * x=Root;while( x != T_NIL)                 //找到结点z要插入的位置,y作为其父节点    {y=x;if (z->key < x->key)x = x->left;elsex = x->right;}z->p = y;if ( y == T_NIL)    //插入第一个结点作为根节点的情况  Root = z;else if (z->key < y->key)y->left = z;else y->right = z;Root = RB_Insert_Fixup(Root,z);    //插入完毕后,对红黑树的颜色进行修正return Root; }
Node* RB_Insert_Fixup(Node *T,Node *z){Node * y=NULL;   while( z->p->color == red)       //违反了性质4,迭代进行修正   {      if (z->p == z->p->p->left)      {          y = z->p->p->right;  if ( y->color == red)    // case 1 叔结点为红色  {             z->p->color = black;    //父节点涂黑 y->color = black;       //叔结点涂黑 z->p->p->color = red;   //祖结点涂红 z = z->p->p;            //向上迭代,更新z的位置  }  else if ( z == z->p->right) //case 2 叔结点为黑色且z为双亲的右孩子  {             z = z->p; Left_Rotate(&T,z); z->p->color = black;    //case2 已转为 case3 继续处理 z->p->p->color = red; Right_Rotate(&T,z->p->p);// while循环终止  }  else                      // case 3 叔结点为黑色且z为双亲的左孩子   {             z->p->color = black; z->p->p->color = red; Right_Rotate(&T,z->p->p);//   while循环终止   }      }  else                      //对称处理  {  y = z->p->p->left;  if ( y->color == red)    // case 1 叔结点为红色  {  z->p->color = black;  y->color = black;  z->p->p->color = red;  z = z->p->p;  }    else if ( z == z->p->left) //case 2 叔结点为黑色且z为双亲的右孩子  {  z = z->p;  Right_Rotate(&T,z);  z->p->color = black;  z->p->p->color = red;  Left_Rotate(&T,z->p->p);//   }  else                      // case 3  {  z->p->color = black;  z->p->p->color = red;  Left_Rotate(&T,z->p->p);   }   }     }   T->color = black;          //保证不会违反性质2,对根节点涂黑   return T;}

4.删除操作

 删除操作同二叉树的删除操作也是类似,也是同样分为:少于两个子结点、有两个子结点的情况,红色标注不同;

1.z的子节点数少于两个:(令y指向z的位置)如果结点y颜色为红色直接删除,因为不会破坏红黑的性质;若为y黑色,调用颜色修复函数,并令其子树x代替z的位置,并把颜色也改变成z的颜色

2.z的子节点数有两个:找到z的后继y,用y代替z的位置,并把y的颜色换成z的颜色,这样不会破坏红黑的性质。但是如果y在之前的位置是黑色,现在由于转移走了,y的右子树x代替了y的位置,此时破坏了这个支树的红黑性质,少了一个黑色结点,需要调用颜色修复函数






如上图1要删除节点4,找到后继y节点5,用y替换z的位置,并颜色也改为z的颜色。同时y之前的位置用x代替,由于y之前为黑色,违反了红黑树性质,所以对于图2中的x就具有了两种颜色,它本身的颜色红色,代替y的颜色黑色。所以x的颜色看做红黑!



综上所述:删除后我们需要判断y结点的颜色,如果y的颜色为红色,同二叉搜索树一样操作;如果为黑色,需要继续调用颜色修复函数。

首先介绍替代的函数RB_Transplant(T,u,v);

void RB_Transplant(Node **T,Node * u,Node * v){  if (u->p == T_NIL)      *T = v;  else if (u == u->p->left)     u->p->left = v;  else u->p->right = v;  v->p = u->p;            //最后这一步无条件执行,假如v是T_NIL也要执行,因为后面颜色修复的需要}


删除函数RB_Delete(T,z),同二叉树一样,只是最后一步需要判断y的颜色;

Node * RB_Delete(Node *T ,Node *z){Node * x =NULL;Node * y =z;enum colors y_original_color = y->color;   //记录下删除前z的颜色    if ( z->left == T_NIL)                     //左子树不存在的情况      {       x = z->right;   RB_Transplant(&T,z,z->right);    }else if ( z->right == T_NIL)              //右子树不存在{   x = z->left;   RB_Transplant(&T,z,z->left);}else                                     //左右都存在的情况{       y = Tree_Minimum(z->right);            //找到后继y   y_original_color = y->color;           //记录下y转移前的颜色   x = y->right;   if ( y->p == z)                       //如果y是z的子结点   {         x->p = y;   }   else   {   RB_Transplant(&T,y,y->right);    //如果y不是z的子结点,用y的右子树代替y的位置   y->right = z->right;   y->right->p = y;   }      RB_Transplant(&T,z,y);           //y替代z的位置 ,不论y是不是T_NIL      y->left = z->left;   y->left->p = y;   y->color = z->color;             //把y的颜色改成z的颜色}if ( y_original_color == black)   //判断y的颜色,若为黑色,需要修复    RB_Delete_Fixup(T,x);return T;}

下面详细介绍颜色修复函数RB_Delete_Fixup(T,x),我们注意到输入参数是结点x,因为y的删除或者转移,x代替了y的位置,不论x红色还是黑色,它都承担了两重颜色属性,红黑或者黑黑,这样才能维持红黑树的性质。

如果x之前为红色,现在把它涂成黑色即可代替被删除或转移走的y;

如果x之前为也为黑色,x即为黑黑。下面分情况讨论:

以下情况是x作为其父节点的左孩子的情况,右孩子的情况对称处理

☆情况1:x的兄弟结点w是红色的;

因为x的黑结点数已为2,所以w必有两个黑结点;对x->p做一次旋转,这时x的兄弟结点w就变成了黑色,转变成了情况2,3,4;为了维护红黑的性质5,旋转操作前把x->p变成红色,把w变成黑色;

如图:


☆情况2:x的兄弟结点w是黑色的,而且w的两个子节点都是黑色的;

如图所示,由于x代表了双重黑色,把w着为红色,把x上的黑色上移一个给其父节点,让父节点让具有双重颜色,

如果父节点B本身是黑色的,这时父节点B具有黑黑的属性,继续当做x迭代处理!

如果父节点B本身是红色的,这时父节点B具有红黑的属性,直接退出循环,把父节点B涂成黑色即可!


☆情况3:x的兄弟结点w是黑色的,w的左孩子是红色的,w的右孩子是黑色的;

交换w与其左孩子的颜色,对w右旋,很容易分析这样处理后不会影响红黑树的性质,这样转变成了情况4


☆情况4:x的兄弟结点w是黑色的,且w的右孩子是红色的;

如图:把w的颜色换成x->p的颜色(不用管x的父结点是红色还是黑色),把x->p的颜色涂成黑色,把w的左孩子涂成黑色,对x的父节点做一次左旋,可以看到不会改变红黑树的性质。

处理前:

从结点B到α的黑色结点数为2,包括B,因为x代表了双重黑色,简记为α(2),则β(2)γ(2)δ(2)ζ(1)ξ(1)

处理后:

x代表一重黑色,则α(2)β(2)γ(2)δ(2)ζ(1)ξ(1) 退出循环


Node* RB_Delete_Fixup(Node * T,Node * x)   //不详细注释,见分析{Node *w=NULL;  while( x != T && x->color == black)      //循环迭代处理  {  if ( x == x->p->left )            //x是其父节点的左孩子  {        w = x->p->right;if (w->color == red)             // case 1 ------> case 2 , case 3 ,case 4 {w->color = black;x->p->color =red;Left_Rotate(&T,x->p);w = x->p->right;}if ( w->left->color == black && w->right->color == black ) //case 2 ------>go on / stop{            w->color = red;x = x->p;}else if ( w->right->color == black)   // case 3 ---->case 4---->stop{           w->left->color = black;   w->color =red ;   Right_Rotate(&T,w);   w = x->p->right ;                   //转成case 4处理    w->color = x->p->color;   x->p->color = black;   w->right->color = black;   Left_Rotate(&T,x->p);   x = T;}else                               // case 4 ------------------->stop{w->color = x->p->color;x->p->color = black;w->right->color = black;Left_Rotate(&T,x->p);    x = T;}  }  else  {  w = x->p->left;  if (w->color == red)                // case 1 ------> case 2 , case 3 ,case 4   {  w->color = black;  x->p->color =red;  Right_Rotate(&T,x->p);  w = x->p->left;  }  if ( w->right->color == black && w->left->color == black ) //case 2 ------>go on/stop  {  w->color = red;  x = x->p;  }  else if ( w->left->color == black)      // case 3 -----> case 4----->stop  {  w->right->color = black;  w->color =red ;  Left_Rotate(&T,w);    w = x->p->left ;                    //转成case 4处理  w->color = x->p->color;  x->p->color = black;  w->left->color = black;  Right_Rotate(&T,x->p);  x = T;  }  else                                  // case 4 -------------->stop  {  w->color = x->p->color;  x->p->color = black;  w->left->color = black;  Right_Rotate(&T,x->p);  x = T;}  }  }  x->color = black;                       //可能由case2退出,那把x涂黑即可,见分析!return T;<pre name="code" class="cpp">}

5.代码例程

/*        CSDN 勿在浮砂筑高台http://blog.csdn.net/luoshixian0992015年5月16日*/#include <STDIO.H> #include <STDLIB.H>enum colors{red,black};//枚举类型typedef struct Node{   struct Node * p;   struct Node *left;   struct Node *right;   int key;   enum  colors color;}Node;Node *T_NIL=NULL;                 //建立全部变量 T_NILNode * Tree_Minimum(Node * T)     //找最小结点{while(T->left != T_NIL)T=T->left;return T;}void Inorder_Tree_Walk(Node * T)  //中序遍历树T,输出{if ( T != T_NIL){Inorder_Tree_Walk(T->left);   //递归其左孩子printf("%d",T->key);        //输出根的关键字if (T->color == 0){printf("-R  ");     //输出结点的颜色}        else{printf("-B  ");}Inorder_Tree_Walk(T->right);  //递归其右孩子}}void Pre_Tree_Walk(Node * T)  //{if ( T != T_NIL){     printf("%d    ",T->key);  //输出根的关键字Pre_Tree_Walk(T->left);   //递归其左孩子      Pre_Tree_Walk(T->right);  //递归其右孩子}}void Left_Rotate(Node **T,Node * x)   //左旋{   Node *y=x->right;     x->right =y->left;   if (y->left != T_NIL)       y->left->p=x;   y->p=x->p;   if(x->p==T_NIL)      *T=y;   else if (x == x->p->left)      x->p->left = y;   else  x->p->right = y;   y->left = x;   x->p=y;}void Right_Rotate(Node **T,Node * y)   //右旋{Node *x=y->left;y->left =x->right;if (x->right != T_NIL)x->right->p=y;x->p=y->p;if(y->p==T_NIL)*T=x;else if (y == y->p->left)y->p->left = x;else  y->p->right = x;x->right = y;y->p=x;}Node* RB_Insert_Fixup(Node *T,Node *z){Node * y=NULL;   while( z->p->color == red)       //违反了性质4,迭代进行修正   {      if (z->p == z->p->p->left)      {          y = z->p->p->right;  if ( y->color == red)    // case 1 叔结点为红色  {             z->p->color = black;    //父节点涂黑 y->color = black;       //叔结点涂黑 z->p->p->color = red;   //祖结点涂红 z = z->p->p;            //向上迭代,更新z的位置  }  else if ( z == z->p->right) //case 2 叔结点为黑色且z为双亲的右孩子  {             z = z->p; Left_Rotate(&T,z); z->p->color = black;    //case2 已转为 case3 继续处理 z->p->p->color = red; Right_Rotate(&T,z->p->p);// while循环终止  }  else                      // case 3 叔结点为黑色且z为双亲的左孩子   {             z->p->color = black; z->p->p->color = red; Right_Rotate(&T,z->p->p);//   while循环终止   }      }  else                      //对称处理  {  y = z->p->p->left;  if ( y->color == red)    // case 1 叔结点为红色  {  z->p->color = black;  y->color = black;  z->p->p->color = red;  z = z->p->p;  }    else if ( z == z->p->left) //case 2 叔结点为黑色且z为双亲的右孩子  {  z = z->p;  Right_Rotate(&T,z);  z->p->color = black;  z->p->p->color = red;  Left_Rotate(&T,z->p->p);//   }  else                      // case 3  {  z->p->color = black;  z->p->p->color = red;  Left_Rotate(&T,z->p->p);   }   }     }   T->color = black;          //保证不会违反性质2,对根节点涂黑   return T;}Node *RB_Insert(Node *Root,Node * z)   //红黑树插入,返回树的根{Node * y=T_NIL;Node * x=Root;while( x != T_NIL)                 //找到结点z要插入的位置    {y=x;if (z->key < x->key)x = x->left;elsex = x->right;}z->p = y;if ( y == T_NIL)    //插入第一个结点作为根节点的情况  Root = z;else if (z->key < y->key)y->left = z;else y->right = z;Root = RB_Insert_Fixup(Root,z);    //插入完毕后,对红黑树的颜色进行修正return Root; }Node * Establish(int *A,int len)   //建立红黑树{   Node * T,*node;   int i=0;   node=NULL;   T_NIL=(Node *)malloc(sizeof(Node));  //建立T_NIL结点   T_NIL->p=NULL;   T_NIL->left=NULL;   T_NIL->right=NULL;   T_NIL->key=-1;   T_NIL->color=black;   T=T_NIL;   for (i=0;i<len;i++)   {       node =(Node *)malloc(sizeof(Node));   node->p =T_NIL;   node->left=T_NIL;   node->right=T_NIL;   node->key=A[i];   node->color=red;   T=RB_Insert(T,node);   }   return T;}void RB_Transplant(Node **T,Node * u,Node * v)  //结点替代函数{  if (u->p == T_NIL)      *T = v;  else if (u == u->p->left)     u->p->left = v;  else u->p->right = v;  v->p = u->p;               //此处赋值无条件,v如果是T_NIL也要进行赋值}Node* RB_Delete_Fixup(Node * T,Node * x){Node *w=NULL;  while( x != T && x->color == black)      //循环迭代处理  {  if ( x == x->p->left )  {        w = x->p->right;if (w->color == red)             // case 1 ------> case 2 , case 3 ,case 4 {w->color = black;x->p->color =red;Left_Rotate(&T,x->p);w = x->p->right;}if ( w->left->color == black && w->right->color == black ) //case 2 ------>go on / stop{            w->color = red;x = x->p;}else if ( w->right->color == black)   // case 3 ---->case 4---->stop{           w->left->color = black;   w->color =red ;   Right_Rotate(&T,w);   w = x->p->right ;                   //转成case 4处理    w->color = x->p->color;   x->p->color = black;   w->right->color = black;   Left_Rotate(&T,x->p);   x = T;}else                               // case 4 ------------------->stop{w->color = x->p->color;x->p->color = black;w->right->color = black;Left_Rotate(&T,x->p);    x = T;}  }  else  {  w = x->p->left;  if (w->color == red)                // case 1 ------> case 2 , case 3 ,case 4   {  w->color = black;  x->p->color =red;  Right_Rotate(&T,x->p);  w = x->p->left;  }  if ( w->right->color == black && w->left->color == black ) //case 2 ------>go on/stop  {  w->color = red;  x = x->p;  }  else if ( w->left->color == black)      // case 3 -----> case 4----->stop  {  w->right->color = black;  w->color =red ;  Left_Rotate(&T,w);    w = x->p->left ;                    //转成case 4处理  w->color = x->p->color;  x->p->color = black;  w->left->color = black;  Right_Rotate(&T,x->p);  x = T;  }  else                                  // case 4 -------------->stop  {  w->color = x->p->color;  x->p->color = black;  w->left->color = black;  Right_Rotate(&T,x->p);  x = T;}  }  }  x->color = black;                       //可能由case2退出,那把x涂黑即可,见分析!也可能有case4退出,把根节点涂黑return T;<pre name="code" class="cpp">}Node * RB_Delete(Node *T ,Node *z){Node * x =NULL;Node * y =z;enum colors y_original_color = y->color;   //记录下删除前z的颜色    if ( z->left == T_NIL)                     //左子树不存在的情况      {       x = z->right;   RB_Transplant(&T,z,z->right);    }else if ( z->right == T_NIL)              //右子树不存在{   x = z->left;   RB_Transplant(&T,z,z->left);}else                                     //左右都存在的情况{       y = Tree_Minimum(z->right);            //找到后继y   y_original_color = y->color;           //记录下y转移前的颜色   x = y->right;   if ( y->p == z)                       //如果y是z的子结点   {         x->p = y;   }   else   {   RB_Transplant(&T,y,y->right);    //如果y不是z的子结点,用y的右子树代替y的位置   y->right = z->right;   y->right->p = y;   }      RB_Transplant(&T,z,y);           //y替代z的位置 ,不论y是不是T_NIL      y->left = z->left;   y->left->p = y;   y->color = z->color;             //把y的颜色改成z的颜色}if ( y_original_color == black)   //判断y的颜色,若为黑色,需要修复   T = RB_Delete_Fixup(T,x);return T;}Node * Tree_Search(Node *T ,int k)  //寻找数k是否在树中,且返回数k的地址{    while(T !=T_NIL && T->key != k){if ( k < T->key)T=T->left;elseT=T->right;}    if ( T == T_NIL){     return NULL;}else {return T;}}void main(){    int A[]={2,5,1,6,3,8,4,9,7};int length = sizeof(A)/sizeof(A[0]); //数组A的长度    Node *T =Establish(A,length);        //建立红黑树,返回根节点T    printf("中序遍历:\n");Inorder_Tree_Walk(T);printf("\n");   //中序遍历输出    printf("先序遍历:\n");              //先序遍历输出    Pre_Tree_Walk(T);printf("\n");    printf("-----------删除操作后-------------\n");        T=RB_Delete(T,Tree_Search(T,2));    T=RB_Delete(T,Tree_Search(T,5));T=RB_Delete(T,Tree_Search(T,7));T=RB_Delete(T,Tree_Search(T,4));printf("中序遍历:\n");Inorder_Tree_Walk(T);printf("\n");printf("先序遍历:\n");    Pre_Tree_Walk(T);    printf("\n");}




2 0
原创粉丝点击