理解红黑树(1)

来源:互联网 发布:usb网络打印服务器 编辑:程序博客网 时间:2024/06/05 18:36
红黑树是一种二叉查找树,它是在1972年由Rudolf Bayer发明的,它的性能优于平衡2叉树(avl树),因为avl树过分追求平衡,avl树要求任何节点的左右子树高度之差不能大于1,而红黑树做到的是任何节点的左右子
树高度差不会超过2倍(左子树的高度不会大于右子树高度的2倍,或者右子树的高度不会大于左子树的高度的2倍),由此看出avl树如果要保持平衡需要付出更多的旋转(左旋,右旋),avl更平衡意味着avl树比红黑树的高度更低,查询时更快一些,但是过多旋转的时间代价大于查询带来的优势。红黑树的应用:jdk中的treeMap,内核中CFS调度根据vruntime(虚拟运行时间),来为进程建立红黑树结构,等等

红黑树的性质:
    1.节点不是红色的就是黑色的。
    2.根节点是黑色的
    3.如果一个节点是红色的,那么他们的孩子都必须是红色的(这一条性质也说明这个节点的父节点肯定是黑色,不会存在两个相邻的红色节点)
    4.对于每个节点到其叶子节点的黑色节点的数量是相同的

C语言的实现:

1.涉及到的数据结构:

  1. typedef struct _node {
  2.   int color;                              //代表节点的颜色1表示黑色节点,0表示红色节点
  3.   struct _node *parent;
  4.  struct _node *left;
  5.   struct _node *right;
  6.   int value;                              //代表节点的值
  7.  } node;
2.节点操作:




  1. 右旋操作围绕4节点旋转,代码如下:
  2.  void rotateRight(node *target){   //如上图4节点就是参数target
  3.      node *left=target->left;       //left节点是2节点
  4.      node *parent=target->parent;  //parentNode是target的父节点
  5.      if(parent!=NULL){
  6.          left->parent= parent;      //如果父节点不为空,设置父节点的父子关系
  7.          if(parent->left==target)
  8.               parent->left=left;    //设置父节点到子节点的关系
  9.          else
  10.              parent->right=left;    //设置父节点到子节点的关系
  11.        }
  12.       node *move=left->right;       //move节点代表3节点
  13.       left->parent=target->parent;                               
  14.       target->parent=left;          //设置target节点到新父节点2的关系
  15.       left->right=target;          //设置left节点到target节点的关系
  16.       if(move!=NULL){              //设置move节点(3节点的父子关系)
  17.           target->left=move;        //target的左节点是3节点
  18.           move->parent=target;      //3节点的父节点target节点
  19.        }
  20.       if(target==root)
  21.          root=left;                 //如果旋转的节点是跟节点,需要更新跟节点引用
  22.  }


  1. void rotateLeft(node*target){       //如上图5节点就是参数target
  2.     node * right=target->right;     //right节点是7节点
  3.     node *parent=target->parent;      //parentNode是target的父节点
  4.     if(parent!=NULL){
  5.         right ->parent= parent;       //如果父节点不为空,设置父节点的父子关系
  6.         if(parent->right ==target)
  7.              parent->right = right;  //设置父节点到子节点的关系
  8.         else
  9.              parent->left=right;     //设置父节点到子节点的关系
  10.      }
  11.      node *move=right ->left;       //move节点代表7节点
  12.      right->parent=target->parent;
  13.      target->parent=right;          //设置target节点到新父节点7的关系
  14.      right ->left=target;             //设置left节点到target节点的关系
  15.      if(move!=NULL){                  //设置move节点(6节点的父子关系)
  16.         target->right=move;          //target的左节点是3节点
  17.         move->parent=target;          //6节点的父节点target节点
  18.       }
  19.      if(target==root)
  20.         root=right;                   //如果旋转的节点是跟节点,需要更新跟节点引用
  21.  
  22.  }
节点的后继节点(节点删除时会用到)


  1. node* successor(node *target){
  2.    node* temp;
  3.    if(target->right!=NULL){ //case1 当target节点有右孩子时,返回右子树中最小的节点,即7节点           temp=target->right; 
  4.       while(temp->left!=NULL)
  5.          temp=temp->left;
  6.       return temp;
  7.    }
  8.    while(temp->parent!=NULL&&temp==temp->parent->right){ 
  9.    //case 2 当左子树为空时,可以理解为比7节点 小的,但是小节点中最大的节点,这个节点就应该是7节点的             左子树中最大的节点,即6节点。
  10.       temp=temp->parent;
  11.    }
  12.    return temp->parent;
  13. }

红黑树的节点的插入过程,和普通的二叉查找树的插入过程类似。只是每个节点多了一个color域,代表节点的颜色(红色,黑色),新插入的节点的颜色是红色的。每个节点插入之后需要看一下当前插入节点的parent节点是否为红色,如果为黑色,则2叉树继续保持红黑树性质3,4,如果为红色,破坏了红黑树性质3,这时需要调整一下节点节点的颜色。所以当插入节点的父节点为红色时,插入后的节点调整需要分为3case

case1:第一种情况的条件是uncle节点不为空,并且uncle节点为红色节点。target节点是parent节点的左孩子或者右孩子,插入target节点之前,会保证数据结构中没有相邻的红色节点,且到叶子节点的黑色数目相同,这时插入target节点,只需要把parent节点,uncle节点变成黑色,grand节点变为红色即可,这样把grand节点的黑色下降到了孩子节点上(parentuncle),保持了没有相邻的红色节点,且到叶子节点黑色数目相同,但是这样把grand节点变成了红色,可能会影响grand的父节点的红黑树性质(如果grand->parent节点为红色),所以需要把target节点变成grand节点,递归grand节点之上的数据结构。


case2是个过渡阶段,目的是让target节点为parent节点的左孩子,这样在后面的右旋时,target节点才不会成为grand的左孩子,正确的做法是交换targetparent节点,然后左旋target节点,进入case3,结果如右图。反之如果在case2中直接右旋grand节点,(目的是保持没有相邻的红色节点,同时黑色节点数量保持一致)会出现下面几种情况(列举几个都不是不可取的):

第一种情况错误的旋转,交换parent节点和grand结果的颜色,显然这样的结果违反了不能出现两个连续的红色节点的性质


第二种情况错误的旋转,交换uncle节点和parent节点的颜色,同时uncle节点为红色,这样会导致uncle左右子树可能出现连续两个红色节点,剩下的错误旋转情况都是显而易见的,不是黑色节点的个数多了就是违反了红色节点不能相邻。


case3情况是插入的target节点是parent节点左孩子,或是右孩子通过case2的操作变成了左孩子,这种情况直接右旋grand节点,并且交换parent节点和grand节点的颜色即可,这种情况不用在递归parent节点的上层数据结构了因为从grand节点的父节点看到的子节点就是黑色的,case3转换完毕后子节点还是黑色的,并且左右子树黑节点的数量维持不变,所以这种情况不用递归父节点的数据结构了。

插入过程的最后需要将root节点置为黑色,这是因为,case1中有可能grand节点就是root节点,case1的最后将root置为了红色,这时root节点没有父节点了,需要保持红黑树的性质,将root节点置为黑色。


  1. node* insert(node*parent,int data){
  2.       if(parent->value>data){      //如果data比parent小,则插入parent的左子树
  3.           if(parent->left==NULL){  //为空直接插入节点
  4.               node* result=malloc(sizeof(node));//设置新节点和parent节点的关系
  5.               result->value=data;
  6.               result->parent=parent;
  7.               parent->left=result;
  8.               result->color=0;
  9.               insertAdjust(result);//新插入节点需要调整一下位置(case1,2,3)
  10.              return result;
  11.           }else{
  12.               return insert(parent->left,data);
  13.                }
  14.  
  15.       }else{
  16.          if(parent->right==NULL){
  17.               node* result=malloc(sizeof(node));
  18.               result->value=data;
  19.              result->parent=parent;
  20.               parent->right=result;
  21.              result->color=0;
  22.              insertAdjust(result);
  23.               return;
  24.          } else{
  25.               return insert(parent->right,data);
  26.           }
  27.       }
  28.  
  29.  }

  1. 插入调整:
  2. void insertAdjust(node *insertNode){
  3.   node* temp=insertNode;
  4.   while(temp!=NULL&&temp!=root&&temp->parent->color==0){       
  5. //如果父节点为红色,就需要调整了
  6.      node *parent=temp->parent;
  7.      node *grandNode=temp->parent->parent;                                       
  8. //不需要判断grand节点是否为null,因为每次 置root为黑色了,如果parent为红色,必然有grand节点
  9.      if(parent==grandNode->left){               //case1
  10.          node *uncle=grandNode->right;
  11.           if(uncle&&uncle->color==0){
  12.                 grandNode->color=0;
  13.                 parent->color=1;
  14.                 uncle->color=1;
  15.                 temp=grandNode;                 //递归grand节点之上的数据结构
  16.           }else{
  17.              if(temp==parent->right){           //case2
  18.                    temp=temp->parent;          //交换父节点和当前插入节点
  19.                    rotateLeft(temp);
  20.               }
  21.                temp->parent->color=1;           //parent节点置为黑色
  22.                temp->parent->parent->color=0;   //grand节点置为红色
  23.                rotateRight(temp->parent->parent);   //右旋grand节点
  24.                }
  25.         }
  26.       else{
  27.            node *uncle=grandNode->left;
  28.           if(uncle&&uncle->color==0){
  29.                grandNode->color=0;
  30.                parent->color=1;
  31.                grandNode->left->color=1;
  32.                temp=grandNode;
  33.           }else{
  34.               if(temp==parent->left){
  35.                   temp=temp->parent;
  36.                   rotateRight(temp);
  37.               }
  38.               temp->parent->color=1;
  39.               temp->parent->parent->color=0;
  40.               rotateLeft(temp->parent->parent);
  41.           }
  42.      }
  43.     root->color=1;
  44.    }
  45. }

继续上篇红黑树的分析,这次是树节点删除的分析

普通二叉树节点删除过程:

普通二叉数节点删除过程分为2种情况(删除target节点):

case1这种情况就是target节点只有一个孩子,无论只有左孩子,还是只有右孩子,在这种情况下删除target节点,这种情况比较简单,就是直接删除target节点,然后重新把parent节点和child节点的父子关系设置好即可

case2中,需要删除的节点target,即有左孩子,又有右孩子,所以按照case1的做法不可取,两个节点不能同时作为parent节点的子节点,所以这时需要寻找一个节点代替target节点,显而易见,这个节点要比parent节点要大,所以结果应该在parent的右子树数中,也就是target节点的左右子树中,同时还有满足两个条件中的任意一个:

1.这个节点的值要要等于c2节点子树的最小值

2.这个节点的值要要等于c1节点子树的最大值

所以这时候可以选择c2子树中最小的replace节点,或者为c1右孩子中最大的那个节点。

这时找到了这个节点,同时这个节点肯定只有一个孩子,如果是target节点的右孩子中的最小值,那么这个最小值节点肯定是没有左孩子的;如果这个节点是target节点中的左孩子中的最大值,那么这个最大值节点是肯定没有右孩子的。找到了这个代替的节点replace节点之后,需要把这个节点删除掉,同时把replace节点的值和target节点的值做个交换,这样target节点就这样间接的删除了

红黑树删除节点后的影响:

第一种情况删除target节点之后,没有任何影响,直接删除,因为target节点是红色的删除不会影响节点到叶子节点黑色节点的数量,同时parent节点和child节点肯定都是黑色的,也不会影响到parent节点和child节点连接导致红色节点相邻。

第二种情况parent节点的颜色任意,但是target节点为黑色,同时child节点为红色,这种情况下删除target节点后,只需要将child节点染为黑色,就可弥补target节点的删除导致的parent左孩子黑色节点少1的情况,同时child的变黑也不影响parent节点的颜色(parent节点颜色可以为红色也可以为黑色)。

第三种情况target节点为黑色,child节点为黑色,这时删除了target节点,很明显parent左子树少了一个黑色节点,不满足红黑树的性质(到任何叶子节点黑色节点数量相同),这时需要协调一下parent右子树和左子树结构,使得黑色节点数量重新平衡一致,而且不出现相邻的红色节点。

节点删除code:

  1. delete(node *target){
  2.   node *delete;
  3.   if(target->left==NULL||target->right==NULL){
  4.       delete=target;//case1
  5.   }else{
  6.       delete=successor(target);//case2
  7.   }
  8.   node *x;
  9.   if(delete->left!=NULL)
  10.       x=delete->left;//设置父子节点关系
  11.   else
  12.       x=delete->right;
  13.   if(delete->parent==NULL)
  14.       root=x;
  15.   x->parent=delete->parent;
  16.   if(delete->parent->left==delete)
  17.       delete->parent->left=x;
  18.   else
  19.       delete->parent->right=x;
  20.   if(target!=delete){
  21.       target->value=delete->value;//更新颜色和值
  22.       target->color=delete->color;
  23.    }
  24.    if(delete->color==1)
  25.       deleteAdjust(x);//协调parent节点左右子树的颜色和位置,重新满足红黑树性质
  26.  }

红黑树节点调整分为4种情况,情况如图case1中,在A节点和B节点中间原来存在被删除的节点,这个节点颜色是黑色的,删除这个节点会导致B节点的左子树黑色节点数量少1,同时单单调整B的左子树的节点颜色是不能保持红黑树性质。这时需要整体调整一下B节点左右子树的结构,B节点左子树黑色节点数量少1,可以采取的方法不外乎两种:

1.B左子树黑色节点数量不变的情况下,调整B右子树黑色节点数量少1,这种方法需要递归B节点的父节点及其以上的数据结构

2.我们会发现在B左子树的结构中,让左子树的黑色节点数量增加一个已经是不可能,所以可以通过左旋转B节点,让左子树的节点多一个,这样就有机会将这个节点置为黑色,重新保持红黑色性质

节点调整会分为4种情况


case1这种情况,当前的情况是B节点的左子树黑色节点数量少1,右子树有红色节点,如果采用方法1不可能,D节点已经为红色,无论更改CE节点的颜色都会破坏红黑树性质(无相邻红色节点),只能采取第二种做法,左旋B节点,同时将D节点(叔父节点)变为黑色,B节点变为红色,这样保持红黑树的性质,D节点的右子树黑色节点数目2个,同时左子树中DBC分支中黑色节点数目也是2个,现在问题变为了B节点的左子树黑色节点的数目和右子树黑色节点数目相同,然后进入case2


case2中,A节点和B节点之间删除了黑色节点,使得A节点的左子树黑色节点数量少1,A节点的颜色可以任意,这时盲目改变A节点的颜色不可取。这时可以采用方法1,就是使得A节点右子树黑色节点数量少一个,可以改变C节点(叔父节点)的颜色,变为红色,不可将DE节点全都变为红色,这样会影响DE的子节点,如果他们的子节点中右红色的,这样会违反红黑树性质(不能有相邻的红色节点)。这样A节点变为黑色,C节点变为红色,这样A节点左右子树黑色节点数目相同了,但是对于A节点的父节点,认为A这一子树整体黑色节点数目少了一个,所以这种情况需要递归A的父节点结构。

case3中,这种情况下,如果用解决方法1,就是让A节点右子树中黑色节点数量少一个,这样只能更新C节点的颜色为红色,这样D节点也为红色,CD节点为相邻的红色节点,违反了红黑树的性质。如果用解决方法2,A节点的左子树黑色节点数量增加1个,如果不管A节点颜色,强行将A节点的颜色变为黑色,不但没有平衡,A的右子树黑色节点也会加1.所以只能左旋节点A,然后在根据情况重新对节点着色,试想,目标就是让A节点位置的节点的左子树黑色节点数量增加1个,左子树黑色节点数量不变,可以肯定的是A节点肯定要变为黑色,所以不用管D节点的颜色,这时如果E节点为黑色,这样导致了旋转过后C节点右子树的黑色节点数量加1,强行更新E节点的颜色为红色,又会导致它的子节点破坏红黑树性质,如果C节点的右子树为红色,就好办了,所以这时需要先右旋C,使得D位置节点的右孩子为红色。右旋C节点,C节点变为红色,D节点变为黑色,这样旋转完毕后,D节点的左右孩子继续保持红黑树性质,然后进入case4

case4中在A节点颜色任意,B节点为黑色,AB节点之间删除了一个黑色节点后,需要调整下数据结构,只需要A节点和C节点交换颜色即可,如图C节点的右子树的黑色节点数量不变,同时左子树结构多了一个黑色节点,弥补了删除的黑色节点。其中D节点颜色我们是不关心的,因为在A节点左旋后,D节点会成为A节点的右孩子,A节点会变为黑色,所以D节点颜色任意。

节点颜色调整的代码:


  1. void deleteAdjust(node* target){
  2.    while(target!=root&&target->color==1){
  3.       node* parent=target->parent;
  4.       if(target==parent->left){
  5.          node* uncle=parent->right;
  6.          if(uncle!=NULL&&uncle->color==0){
  7.             uncle->color=1;                                //case1
  8.             parent->color=0;
  9.             rotateLeft(parent);
  10.             uncle=uncle->left;
  11.           }
  12.          if(uncle!=NULL&&uncle->left->color==1&&uncle->right->color==1){
  13.             uncle->color=0;                            //case2
  14.             target=target->parent;
  15.          }
  16.          else{
  17.              if (uncle!=NULL&&uncle->left->color==0){
  18.                   uncle->left->color=1;
  19.                   uncle->color=0;
  20.                   rotateRight(uncle);                  //case3
  21.                   uncle=target->parent->right;
  22.               }
  23.             uncle->color=target->parent->color;        //case4
  24.             parent->color=1;
  25.             uncle->right->color=1;
  26.             rotateLeft(parent);
  27.             target=root;
  28.          }
  29.       }else{
  30.          node* uncle=parent->left;
  31.          if(uncle!=NULL&&uncle->color==0){
  32.            uncle->color=1;
  33.            parent->color=0;
  34.            rotateRight(parent);
  35.            uncle=uncle->right;
  36.          }
  37.          if(uncle!=NULL&&uncle->right->color==1&&uncle->left->color==1){
  38.            uncle->color=0;
  39.            target=target->parent;
  40.          }
  41.          else{
  42.              if(uncle!=NULL&&uncle->right->color==0){
  43.                   uncle->right->color=1;
  44.                  uncle->color=0;
  45.                   rotateLeft(uncle);
  46.                   uncle=target->parent->left;
  47.              }
  48.              uncle->color=target->parent->color;
  49.              parent->color=1;
  50.              uncle->left->color=1;
  51.              rotateRight(parent);
  52.              target=root;
  53.          }
  54.       }
  55.      root->color=1;
  56.    }
  57.  }