zoukankan      html  css  js  c++  java
  • 树 -- 红黑树

    背景知识


      通过上一篇文章的介绍,我们了解到,对二叉查找树的插入和删除会影响树整体的"平衡"性.树显得越不"平衡",在上面进行各种操作所需的时间也就越长.因此,我们希望有一颗能够随时随地保持"平衡性"的树,及时插入和删除元素,也可以通过调整来保持树的"平衡性".

      在本章我们首先介绍一个不是完全"平衡"的,而是"相对平衡"的树 -- 红黑树.我们可以判断到,要使得树显得越"平衡",则需要对树进行的修改也就可能越多.而红黑树可以认为是一种折中,使用一定的调整步骤,来确保相对的"平衡".究竟红黑树的"相对平衡"是怎么样的?如果取得这种相对的平衡?让我们一一道来.

    红黑树的性质


      上一节我们提到, 红黑树的目标时通过较少的调整步骤来获得"相对平衡".那么到底什么样的树可以算是红黑树呢?

      (1)每个节点是红色的或黑色的;

      (2)根节点时黑色的;

      (3)叶子节点时黑色的;

      (4)任一红色节点的子节点只能是黑色节点;

      (5)树内任一节点到到叶子节点的各个路径上的黑色节点数目相同;

      满足以上5个条件的树,我们成为红黑树.

      

      推论(1): 从任一节点到后代叶节点的所有简单路径中,最长的是最短的至多两倍.

      证明:因为树内任一节点到叶子节点的路径上黑色节点的数目相同(性质 5),所以假设某条路经黑色节点的数目为n,那么其所相对的另一条路径上的黑色节点数目也为n.这两条路径长度相差最大的情况也就是一条路经只有n个黑色节点,而另一条路径含有尽可能多的红色节点.根据(性质 4),n个黑色节点所形成的n-1个间隙中最多只能包含n-1个红色节点,因此,最长的路径上包含n + n - 1 = 2n - 1个节点,所以最长的路径的长度小于最短的路径长度的两倍.

      推论(2):一颗具有n个节点的红黑树的高度最多为2lg(n + 1)

      证明:

        [1]根据替代法,可以证明红黑树上某个节点的子树中至少包含2bh(x)-1个节点.

        [2]假设红黑树的高度为h,根据性质(4),根的黑高度bh(root) >= h/2

          因此,      n  >= 2bh(x)-1>= 2h/2-1

             ->  h <= 2lg(n+1)

      通过上面的论证,我们得知,具有n个节点的红黑树的高度的上限是2 lg(n + 1),因此对其进行查找操作的时间复杂度为O(lgn).

    查找


      对红黑的查找操作与对二叉查找树的查找操作相同.因为红黑树也是一颗二叉查找树,它的红黑性质是用于对插入,删除操作时的"平衡"恢复操作.对查找操作没有影响.

    插入


       对红黑树中节点的插入可以分为两部分:

        第一步, 将红黑树按照二叉查找树的插入操作来添加节点,并将节点的颜色赋值为红色;

        第二步,通过旋转,赋值等操作来调整节点间关系,保持红黑树整体的性质.

      第一步的操作可以参考上一篇文章.

      在此讨论一下讲新节点赋值为红色的原因:对于红黑树内的节点,只能时红色或黑色.如果将新生节点赋值为黑色,那么与这个新节点相关的简单路径的黑色节点树都增加了1,违背了红黑书的性质(5),需要为其余所有的简单路径都增加1个黑色节点,调整设计到的节点数太多;如果将新生节点赋值为红色,违背了红黑树的性质(4),也可能违背性质(2).可以通过旋转,赋值操作来恢复平衡.较为便利.

      

      我们来详述一下红黑树的插入调整操作:我们在红黑树上插入了一个红色节点,可能会违背了红黑树的性质(4)与性质(2).

      当违背性质(2)时,表明树还是空树,只需将刚插入的红色节点染为黑色即可满足红黑树的要求.

      

      当违背性质(1)时,表明新插入节点的父节点也为红色! 我们最直接的想法就时通过调整在这两个红色节点之间插入一个黑色节点.

      那么能否从新插入节点的兄弟节点所在分支中选择一个黑色节点来代替呢? 新插入的节点位于树的最低层,它的父节点很可能没有其他子节点!

      那么只能向上寻找可以使用的黑色节点了.下面这种情况可以通过旋转操作来解决问题:  

       "1为刚插入的节点"

      首先将节点2右旋,然后交换2,3的颜色.在这之后两个红色节点就分开了, 并且性质(5)也没有被违背.如果1是2的右儿子,可以通过左旋,变为作为儿子后在进行上述操作.

           

      但是,这里有一点非常危险的漏洞! 如果节点3的右儿子为红色的话,节点3被变为红色后,又出现了红色节点(D)的父节点(3)是红色节点的情况!

      所以在旋转之前,我们需要确保3的另一个儿子为黑色节点.

      

      如果遇到3的另一个儿子为红色的情况,我们就将2,3,4的颜色反转(这个操作仍然能保证性质5),这样,节点1的红色就不再违背性质4了,但是节点3可能会违背.所以将节点3看做待处理节点.

     1 template<class T>
     2 void RBTree<T>::__insertFixUp(RBTNode<T> *x){
     3     RBTNode<T> *parent;
     4     while(((parent = x->parent) != nil ) && (parent->color == RBTNode<T>::RED)){
     5         RBTNode<T> *gparent = parent->parent;
     6 
     7         if(parent == gparent->leftChild){ // 父亲是祖父的左儿子的情况
     8             RBTNode<T> *uncle = gparent->rightChild;
     9             if(uncle->color == RBTNode<T>::RED){ // 节点4为红色节点的情况
    10                 parent->color = RBTNode<T>::BLACK;
    11                 uncle->color = RBTNode<T>::BLACK;
    12                 gparent->color = RBTNode<T>::RED;
    13             
    14                 x = gparent;
    15                 continue;
    16             }
    17 
    18             //能做到这里,说明节点4已经是黑色节点了.
    19             if(x == parent->rightChild){ 
    20                 __leftRotate(parent);
    21                 RBTNode<T> *tmp = x;
    22                 x = parent;
    23                 parent = tmp;
    24             }// 将节点1是节点2的右儿子的情况转换为节点1是节点2的左儿子.
    25 
    26             parent->color = RBTNode<T>::BLACK;
    27             gparent->color = RBTNode<T>::RED;
    28             
    29             __rightRotate(gparent);//右旋
    30 
    31         }else{ //父亲是祖父的右儿子的情况,类比上面的推理.
    32             RBTNode<T>* uncle = gparent->leftChild;
    33             if(uncle->color == RBTNode<T>::RED){
    34                 parent->color = RBTNode<T>::BLACK;
    35                 uncle->color = RBTNode<T>::BLACK;
    36                 gparent->color = RBTNode<T>::RED;
    37 
    38                 x = gparent;
    39                 continue;
    40             }
    41             //Now his uncle is black
    42             if(x == parent->leftChild){
    43                 __rightRotate(parent);
    44                 RBTNode<T> *tmp = parent;
    45                 parent = x;
    46                 x = tmp;
    47             }//We change the left case to right case
    48 
    49             parent->color = RBTNode<T>::BLACK;
    50             gparent->color = RBTNode<T>::RED;
    51 
    52             __leftRotate(gparent);
    53         }
    54     }
    55 
    56     root->color = RBTNode<T>::BLACK; // 根节点必须为黑色
    57 }

    删除


      红黑树的删除操作分为两步:

      1)按照二叉查树的方法来删除节点;

      2)被删除的节点为红色的时候,会造成树当前状态违背性质5,需要进行调整;

      当被删除的节点为红色时,被删除节点所能到叶子节点的所有简单路径的上的黑色节点数目都减少了1.需要补充回来已满足性质(5).

      在这里,我们认为:被删除的黑色附着到了代替它的那个节点上,并没有消失!这样的化,性质5就也就得到了满足.也就是说代替原节点的那个节点是红色的话,我们就认为它现在是红+黑色;是黑色的话,我们就认为它现在黑+黑色.

      但是此时,性质1被破坏了(红黑树的节点只能时红色或者黑色).因此我们需要把这个多余的黑色去掉.

      当代替节点是红+黑时,可以使用将红色剔除掉,只保留黑色.红黑树的5个性质都能得到满足;

      当代替节点是黑+黑时,就必须寻找一个红色节点,将多余的黑色传递给它.  

      在这里需要分情况讨论(我们以代替节点是父亲节点的左儿子为例):

        [1].当代替节点的兄弟节点是红色时,那么它兄弟的子节点必然都是黑色节点.

               

        通过左旋操作,就将这种情况转化为了代替节点的兄弟节点是黑色的情况.

        [2].当代替节点的兄弟节点时黑色时,要依据其兄弟节点的子节点来分情况讨论.

          [2.1]当兄弟的子节点都是黑色节点时,将兄弟节点2的颜色转变为红色,此时从3到叶子节点的所有简单路径的黑色节点数都少了1.为了解决这个问题,我们将1中多余的黑色传递给2,这样性质5又得到了满足.

              

           此时,节点2中有了多余的黑色.继续处理节点2.

          [2.2]当兄弟节点的左儿子为红色时,可通过旋转来转换为右儿子为红色的情况

          

           因为3之前为红色节点,所以其左儿子M必定为黑色节点(性质4).

          [2.3]当兄弟节点的右儿子为红色时,先将2进行依次右旋,然后进行一系列染色操作:1中多余的黑色染给2, 2之前的颜色染给3, 3之前的颜色染给4.

          

     1 vvoid RBTree<T>::__ereaseFixUp(RBTNode<T> *x){
     2     RBTNode<T> *parent  = x->parent;
     3     RBTNode<T> *brother;
     4 
     5     while(x != root && x->color = RBTNode<T>::BLACK){
     6         if(parent->leftChild = x){
     7             brohter = parent->rightChild;
     8 
     9             if(brohter->color == RBTNode<T>::RED){
    10                 brohter->color = RBTNode<T>::BLACK;
    11                 parent->color = RBTNode<T>::RED;
    12 
    13                 __leftRotate(parent);
    14                 brother = parent->rightChild;
    15             }
    16             if(brother->leftChild->color == RBTNode<T>::BLACK && brother->rightChild->color == RBTNode<T>::BLACK){
    17                 brother->color = RBTNode<T>::RED;
    18                 x = parent;
    19             }else if(brother->leftChild->color == RBTNode<T>::RED){
    20                 brother->color = RNTNode<T>::RED;
    21                 brother->leftChild->color = RBTNode<T>::BLACK;
    22                 __leftRotate(brother);
    23 
    24                 brother = parent->rightChild;
    25             }
    26             brother->color = parent->color;
    27             parent->color = RBTNode<T>::BLACK;
    28             brother->rightChild->color = RBTNode<T>::BLACK;
    29 
    30             __leftRotate(parent);
    31 
    32             x = root;
    33         }else{
    34             //代替节点是其父亲的右儿子的情况
    35             //与上述代码相反;
    36         }
    37     }
    38     x->color = RBTNode<T>::BLACK;
    39 }

    参考


      算法导论

      Linux Kernel

      shywang12345的csdn博文

  • 相关阅读:
    DP:Multiplication Puzzle(POJ 1651)
    Heap:Expedition(POJ 2431)
    velocity.js 动画插件
    ES6 新特性
    ps p图
    php 建站 多域名配置 自定义重定向
    移动端开发 资源分享
    拖拽 初体验
    颜色选择器 rgb 与16进制 颜色转换
    web 常用颜色
  • 原文地址:https://www.cnblogs.com/carlsama/p/4148414.html
Copyright © 2011-2022 走看看