zoukankan      html  css  js  c++  java
  • 红黑树学习总结

    一、红黑树特性

    1、所有节点不是黑色就是红色。

    2、根节点为黑色。

    3、null节点为黑色,区别于叶子节点。

    4、如果当前节点为红色节点,孩子则全为黑色节点(反之不然)。

    5、任意节点到叶子节点的所有路径都包含相同的黑色节点(保持平衡的重要原因)。

    三、红黑树修正方式

    1、染色:为了符合红黑树的规则特性而改变当前节点的颜色。

    2、旋转 :平衡二叉树的操作共性

    (1)左旋

      演示:

     

      代码:

     public void leftRotate(RBNode<T> x){
            if(x==null) return;
    
            RBNode<T> y = x.right;
            x.right = y.left;
            if(y.right!=null){
                y.right.parent = x;
            }
    
            y.parent = x.parent;
            if(x.parent==null){
                mRoot = y;
            }else{
                if(x==x.parent.left){
                    x.parent.left = y;
                }else{
                    x.parent.right = y;
                }
            }
            //设置x与y之间的关系
            x.parent = y;
            y.left = x;
    
        }

    (2)右旋

      演示:

      代码:

     1  public void rightRotate(RBNode<T> x){
     2         if(x==null) return;
     3 
     4         RBNode<T> y = x.left;
     5         x.left = y.right;
     6         if(y.right!=null){
     7             y.right.parent = x;
     8         }
     9         y.parent = x.parent;
    10         if(x.parent==null){
    11             mRoot = y;
    12         }else{
    13             if(x==x.parent.left){
    14                 x.parent.left = y;
    15             }else{
    16                 x.parent.right = y;
    17             }
    18         }
    19         x.parent = y;
    20         y.right = x;
    21     }

     四、红黑树插入节点

    步骤:1、按照二叉树的插入规则插入节点(节点为红色)。2、修正红黑树。

    插入节点为红色:插入黑色节点,一定破坏红黑树结构(特性5)。插入红色节点,可能破坏特性(2,4)

    情况分析

    分析插入节点的父节点为祖父节点的左孩子,如果插入节点的父节点为祖父节点的右孩子则为镜像操作。

    核心步骤:看父亲节点,看叔叔节点,看插入位置。

      *如果插入红色节点的父节点为黑色,树不需要调整。

      *如果插入红色节点的父节点为红色,树需要调整。

        (1)叔叔节点为红色。

        (2)叔叔节点为黑色,添加节点为父亲节点的左孩子。

        (3)叔叔节点为黑色,添加节点为父亲节点的右孩子。

    case1:父亲节点为红色,叔叔节点也为红色:

    上边的目标节点为左孩子,下边的目标节点为右孩子。

    解释:

      *将父节点和叔叔节点全部染成黑色(节点T满足了特性四),但是这样父亲和叔叔节点的分支都多了一个黑色;

      *将祖父节点染成红色,这样祖父节点的两个分支满足了所有特性,但是我们需要检验祖父节点是否符合红黑树的特性;

      *将祖父节点当做插入节点,向根节点进行回溯修改。

    停止条件:一直向上回溯,直到父节点变为黑色,或者达到树根为止。

    case2:父亲节点为红色,叔叔节点为黑色,插入节点为父亲节点的左孩子。

    修正过程:

     解释:

      (1)将父亲节点染成黑色。

      (2)将祖父节点染成红色。

      (3)对祖父节点进行右旋。

    case3:父亲节点为红色,叔叔节点为黑色,插入节点为父亲节点的右孩子。

    修正过程:

    解释:

      (1)对父节点进行左旋操作、

      (2)将父节点当做插入节点,执行case2情况的红黑树修正。

    五、红黑树删除节点

    1、平衡二叉树操作方式删除节点

    删除case:

      (1)待删节点为叶子节点,直接删除。

      (2)待删节点只存在一个孩子,使用孩子替换待删节点。

      (3)待删节点存在左右孩子,使用中序遍历结果中的后继节点替换待删节点(实际回到了case2)。

    删除节点时重点:

      case1:直接解除待删节点与父节点之间的关系。

      case2:建立待删节点的父节点与替换节点的关系。

      case3:1、建立待删节点的父节点与替换节点的关系;2、建立替换节点的父节点与替换节点子节点之间的关系;3、建立替换节点与待删节点的子节点之间的关系。

    2、红黑树修正:分析父节点为祖父节点的左孩子,右孩子则为镜像操作。

    删除节点为黑色,需要修正红黑树。

    删除节点判断:删除情况的case1和case,删除节点为实际被删节点。删除情况的case3,删除节点为替换节点(因为替换节点被移动到被删节点的位置,并染成与实际被删节点相同的颜色)。

    核心思想:从兄弟节点借调黑色节点。

    case1:被删节点为黑色节点,兄弟节点为红色

    修正过程:

     解释:

      *将父节点涂红,将兄弟节点涂黑,然后将当前节点的父节点进行支点左旋。这样就转换为兄弟节点为黑色的情况

    case2:当前节点为黑色,兄弟节点为黑色,兄弟节点的左右孩子都为黑色

    修成过程:

    解释:

      *将兄弟节点涂红,将当前节点指向其父节点,将其父节点指向当前节点的祖父节点,继续往树根递归判断以及调整;

    case3:当前节点为黑色,兄弟节点为红色,近侄子为红色(兄弟节点的左孩子),远侄子为黑色(兄弟节点的右孩子)

    修正过程:

     解释:

      *把当前节点的兄弟节点涂红,把兄弟节点的左子节点涂黑,然后以兄弟节点作为支点做右旋操作。

     case4:当前节点为黑色节点,兄弟节点为黑色,近侄子为任意色(兄弟节点的左孩子),远侄子为红色(兄弟节点的右孩子)

    修正过程:

     解释:

      *把兄弟节点涂成父节点的颜色,再把父节点涂黑,把兄弟节点的右子节点涂黑,然后以当前节点的父节点为支点做左旋操作。

    六、红黑树完整代码(java实现)

      1 package RBTree;
      2 
      3 /**
      4  * 红黑树演示
      5  * @param <T>
      6  */
      7 public class RBTree<T extends Comparable<T>> {
      8 
      9     public RBNode<T> mRoot = null;
     10     public static boolean RED = true;
     11     public static boolean BLACK = false;
     12     //节点结构
     13     class RBNode<T extends Comparable<T>>{
     14        boolean color;
     15        T key;
     16        RBNode<T> right;
     17        RBNode<T> left;
     18        RBNode<T> parent;
     19 
     20         public RBNode(boolean color, T key, RBNode<T> right, RBNode<T> left, RBNode<T> parent) {
     21             this.color = color;
     22             this.key = key;
     23             this.right = right;
     24             this.left = left;
     25             this.parent = parent;
     26         }
     27         public T getKey(){
     28             return this.key;
     29         }
     30 
     31         public boolean getColor(){
     32             return this.color;
     33         }
     34         @Override
     35         public String toString() {
     36             return "RBNode{" +
     37                     "color=" + color +
     38                     ", key=" + key +
     39                     ", right=" + right +
     40                     ", left=" + left +
     41                     ", parent=" + parent +
     42                     '}';
     43         }
     44     }
     45 
     46     /**
     47      * 左旋:设父节点x,x的左子节点y
     48      * 1、
     49      * @param x
     50      */
     51     public void leftRotate(RBNode<T> x){
     52         if(x==null) return;
     53 
     54         RBNode<T> y = x.right;
     55         x.right = y.left;
     56         if(y.right!=null){
     57             y.right.parent = x;
     58         }
     59 
     60         y.parent = x.parent;
     61         if(x.parent==null){
     62             mRoot = y;
     63         }else{
     64             if(x==x.parent.left){
     65                 x.parent.left = y;
     66             }else{
     67                 x.parent.right = y;
     68             }
     69         }
     70         //设置x与y之间的关系
     71         x.parent = y;
     72         y.left = x;
     73 
     74     }
     75 
     76     /**
     77      * 右旋:左旋的镜像
     78      * @param x
     79      */
     80     public void rightRotate(RBNode<T> x){
     81         if(x==null) return;
     82 
     83         RBNode<T> y = x.left;
     84         x.left = y.right;
     85         if(y.right!=null){
     86             y.right.parent = x;
     87         }
     88         y.parent = x.parent;
     89         if(x.parent==null){
     90             mRoot = y;
     91         }else{
     92             if(x==x.parent.left){
     93                 x.parent.left = y;
     94             }else{
     95                 x.parent.right = y;
     96             }
     97         }
     98         x.parent = y;
     99         y.right = x;
    100     }
    101 
    102     public boolean isRED(RBNode<T> node){
    103        return node!=null&&node.getColor();
    104     }
    105     /**
    106      * 插入节点接口
    107      * @param key
    108      */
    109     public void insert(T key){
    110         RBNode node = new RBNode<T>(RED,key,null,null,null);
    111         insert(node);
    112     }
    113 
    114     /**
    115      * 在红黑树中插入节点,
    116      * 同二叉搜索树的方法相同
    117      * @param node
    118      */
    119     public void insert(RBNode<T> node){
    120         RBNode<T> k = mRoot;
    121         RBNode<T> current = null;
    122         //定位
    123         while(k!=null){
    124             current = k;
    125             int res = node.getKey().compareTo(k.getKey());
    126             if(res<0){
    127                 k = k.left;
    128             }else{
    129                 k = k.right;
    130             }
    131         }
    132         node.parent = current;
    133         //插入节点
    134         if(current!=null){
    135             int res = node.getKey().compareTo(current.getKey());
    136             if(res<0){
    137                 current.left = node;
    138             }else{
    139                 current.right = node;
    140             }
    141             insertFixUp(node);
    142         }else{
    143             mRoot = node;
    144         }
    145     }
    146 
    147     /**
    148      * 修正插入节点后的红黑树
    149      * 如果插入节点的父节点为红色,违背红黑树特性(1、红节点的孩子全为黑节点;2、路径中的黑节点个数)。
    150      * case:考虑父节点为祖父节点的左孩子,如果是右孩子则为镜像
    151      * 1、父节点的兄弟节点为红色
    152      * 2、父节点的兄弟节点为黑色,插入节点左为父节点的左孩子
    153      * 3、父节点的兄弟节点为黑色,插入位置作为父节点的右孩子
    154      * @param node
    155      */
    156     public void insertFixUp(RBNode<T> node){
    157             RBNode<T> parent,gparent;
    158             while((parent=node.parent)!=null && isRED(parent)){
    159                 gparent = parent.parent;
    160                 //父节点为祖父节点的左孩子
    161                 if(parent==gparent.left){
    162                     RBNode<T> uncle = gparent.right;
    163                     //case1:叔叔节点为红色
    164                     if(uncle!=null && isRED(uncle)){
    165                         parent.color = BLACK;
    166                         uncle.color = BLACK;
    167                         gparent.color = RED;
    168                         node = gparent;
    169                         continue;
    170                     }
    171                     //case3:叔叔节点为黑色,插入节点为父节点的右孩子
    172                     if(node==parent.right){
    173                         /*
    174                         对父节点进行左旋,变成第case2
    175                          */
    176                         leftRotate(parent);
    177                         //调整父节点与子节点的关系
    178                         RBNode<T> tmp = parent;
    179                         parent = node;
    180                         node = tmp;
    181                     }
    182                     //case2:叔叔节点为黑色,插入节点为父节点的左孩子
    183                     parent.color = BLACK;
    184                     gparent.color = RED;
    185                     rightRotate(gparent);
    186                 }else{  //父节点为祖父节点的右孩子,基本为镜像操作
    187                     RBNode<T> uncle = gparent.left;
    188 
    189                     if(uncle!=null && isRED(uncle)){
    190                         uncle.color = BLACK;
    191                         parent.color = BLACK;
    192                         gparent.color = RED;
    193                         node = gparent;
    194                         continue;
    195                     }
    196                     if(node==parent.left){
    197                         rightRotate(parent);
    198                         RBNode<T> tmp = parent;
    199                         parent = node;
    200                         node = tmp;
    201                     }
    202                     parent.color = BLACK;
    203                     gparent.color = RED;
    204                     leftRotate(gparent);
    205                 }
    206             }
    207             this.mRoot.color = BLACK;
    208     }
    209 
    210     /**
    211      * 删除节点
    212      * 1、删除节点:保证二叉树规则
    213      * 2、调整结构:保证红黑树规则。
    214      *      (1)删除节点为红色,不需要进行调整
    215      *      (2)删除节点为黑色,需要进行调整
    216      * @param key
    217      */
    218     public void remove(T key){
    219         RBNode<T> node;
    220         if((node = search(mRoot,key))!=null){
    221             remove(node);
    222         }
    223     }
    224     public void remove(RBNode<T> node){
    225         RBNode<T> child,parent;
    226         boolean color;
    227         /*
    228         待删除节点同时存在左右孩子
    229         找到后继节点:中序遍历的下一个节点
    230          */
    231         if(node.left!=null && (node.right!=null)){
    232             RBNode<T> replace = node.right;
    233             while(replace.left!=null){
    234                 replace = replace.left;
    235             }
    236             //处理待删节点的父节点与后继节点的关系
    237             if(node.parent!=null){
    238                 if(node==node.parent.left){
    239                     node.parent.left = replace;
    240                 }else{
    241                     node.parent.right= replace;
    242                 }
    243             }else{
    244                 mRoot = replace;
    245             }
    246             //建立后继节点的子节点与父节点的关系
    247             //后继节点一定不存在左孩子。
    248             child = replace.right;
    249             parent = replace.parent;
    250             color = replace.color;
    251             //后继节点的父节点为待删节点
    252             if(parent==node){
    253                 parent = replace;
    254             }else{
    255                 if(child!=null){
    256                     child.parent = parent;
    257                 }
    258                 parent.left = child;
    259                 //处理后继节点与待删节点的子节点的关系
    260                 replace.right = node.right;
    261                 node.right.parent = replace;
    262             }
    263             replace.parent = node.parent;
    264             replace.color = node.color;
    265             replace.left = node.left;
    266             node.right.parent = replace;
    267             if(color == BLACK){
    268                 removeFixUp(child,parent);
    269             }
    270             //删除节点
    271             node = null;
    272         }else{
    273             //被删除的节点为叶子节点或只有一个孩子
    274             if(node.left!=null){
    275                 child = node.left;
    276             }else{
    277                 child = node.right;
    278             }
    279             parent = node.parent;
    280             color = node.color;
    281             if(child!=null){
    282                 child.parent = parent;
    283             }
    284             if(parent!=null){
    285                 if(node == node.parent.left){
    286                     node.parent.left = child;
    287                 }else{
    288                     node.parent.right = child;
    289                 }
    290             }else{
    291                 mRoot = child;
    292             }
    293             if(color == BLACK){
    294                 removeFixUp(child,parent);
    295             }
    296             node = null;
    297         }
    298     }
    299 
    300     /**
    301      * 为了保证删除节点的父节点左右两边黑色节点数一致,需要重点关注父节点没删除的那一边节点是不是黑色。
    302      *  * 如果删除后父亲节点另一边比删除的一边黑色多,就要想办法搞到平衡。
    303      *  * 1、把父亲节点另一边(即删除节点的兄弟树)其中一个节点弄成红色,也少了一个黑色。
    304      *  * 2、或者把另一边多的节点(染成黑色)转过来一个
    305      *  *
    306      *  * 1)、当前节点是黑色的,且兄弟节点是红色的(那么父节点和兄弟节点的子节点肯定是黑色的);
    307      *  * 2)、当前节点是黑色的,且兄弟节点是黑色的,且兄弟节点的两个子节点均为黑色的;
    308      *  * 3)、当前节点是黑色的,且兄弟节点是黑色的,且兄弟节点的左子节点是红色,右子节点时黑色的;
    309      *  * 4)、当前节点是黑色的,且兄弟节点是黑色的,且兄弟节点的右子节点是红色,左子节点任意颜色。
    310      * @param node
    311      * @param parent
    312      */
    313     public void removeFixUp(RBNode<T> node,RBNode<T> parent){
    314             RBNode<T> other;
    315             RBNode<T> root = mRoot;
    316             while((node==null || node.color == BLACK) && node!=root){
    317                 if(node == parent.left){
    318                     other = parent.right;
    319                     //case1:兄弟节点为红色
    320                     if(other!=null && isRED(other)){
    321                         other.color = BLACK;
    322                         parent.color = RED;
    323                         leftRotate(parent);
    324                         other = parent.right;
    325                     }
    326                     //case2:兄弟节点为黑色,其左右孩子也为黑色。
    327                     if((other.left == null || other.left.color==BLACK) &&
    328                             (other.right==null || other.right.color==BLACK)){
    329                         other.color = RED;
    330                         node = parent;
    331                         parent = node.parent;
    332                     }else{
    333                         //case3:兄弟节点为黑色,兄弟节点的左孩子为红色色,右孩子为黑色。
    334                         if(other.right == null || other.right.color==BLACK){
    335                             other.left.color = BLACK;
    336                             other.color = RED;
    337                             rightRotate(other);
    338                             other = parent.right;
    339                         }
    340                         //case4:兄弟节点为黑色,且兄弟节点的右孩子红色,左孩子任意颜色
    341                         other.color = parent.color;
    342                         parent.color = BLACK;
    343                         other.right.color = BLACK;
    344                         leftRotate(parent);
    345                         node = root;
    346                         break;
    347                     }
    348                 }else{
    349                     other = parent.left;
    350                     if(other!=null && isRED(other)){
    351                         other.color = BLACK;
    352                         parent.color = RED;
    353                         rightRotate(parent);
    354                         other = parent.left;
    355                     }
    356                     if((other.left==null || other.left.color==BLACK) &&
    357                             (other.right==null || other.right.color==BLACK)){
    358                        other.color = RED;
    359                        node = parent;
    360                        parent = node.parent;
    361                     }else{
    362                         if(other.left==null || other.left.color==BLACK){
    363                             other.right.color = BLACK;
    364                             other.color = RED;
    365                             leftRotate(other);
    366                             other = parent.left;
    367                         }
    368                         other.color = parent.color;
    369                         parent.color = BLACK;
    370                         other.left.color = BLACK;
    371                         rightRotate(parent);
    372                         node = root;
    373                         break;
    374                     }
    375                 }
    376             }
    377             if(node != null){
    378                 node.color = BLACK;
    379             }
    380     }
    381 
    382     /**
    383      * 搜索key
    384      * 返回key所在节点
    385      * @param key
    386      * @return
    387      */
    388     public RBNode<T> search(RBNode<T> mRoot,T key){
    389         if(mRoot==null){
    390             return null;
    391         }
    392         RBNode<T> current = mRoot;
    393         while(current!=null){
    394             int res = key.compareTo(current.getKey());
    395             if(res<0){
    396                 current = current.left;
    397             }else if(res>0){
    398                 current = current.right;
    399             }else{
    400                 //找到节点
    401                 return current;
    402             }
    403         }
    404         return null;
    405 
    406     }
    407     
    408 }
    View Code 

    博客参考:

    http://dandanlove.com/2018/03/18/red-black-tree/(基本参考了此博客)

    https://zhuanlan.zhihu.com/p/24367771

    http://blog.csdn.net/eson_15/article/details/51144079

    将将当前节点指向其父节点,将其父节点指向当前节点的祖父节点,继续往树根递归判断以及调整;

  • 相关阅读:
    var 全局变量 局部变量
    C#的发展历程第五
    优雅的处理Redis访问超时
    《集体智慧编程》读书笔记10
    《集体智慧编程》读书笔记9
    C#码农的大数据之路
    C#码农的大数据之路
    C#码农的大数据之路
    C#码农的大数据之路
    .NET遇上Docker
  • 原文地址:https://www.cnblogs.com/dream-flying/p/13246810.html
Copyright © 2011-2022 走看看