zoukankan      html  css  js  c++  java
  • 红黑树详解

    在介绍红黑树之前,有必要对树的概念以及相关理论作一个概述:

    1. 树的导览

    树由节点(Nodes)和 边(edges)构成。树有根节点(root),边(deges),父节点(parent),子节点(child),叶节点(leaf)。如果最多只允许两个子节点,即所谓的二叉树(binary tree)。不同的节点如果拥有相同的父节点,则彼此互为兄弟节点(siblings)。根节点至任何节点之间有唯一路径(path),路径所经过的边数,成为路径长度(length)。根节点至任一节点的路径长度,即所谓该节点的深度(depth)。根节点的深度永远是0。 某节点至其最深子节点(叶节点)的路径长度,成为该节点的高度(height)。整棵树的高度,便以根节点的高度来代表。节点A->B 之间如果存在(唯一)一条路径,那么A 称为B的祖代(ancestor),B称为A的子代(descendant)。任何节点的大小(size)是指其所有子代(包括自己)的节点总数。

    1.1 二叉搜索树(binary search tree)

    所谓二叉树(binary tree),其意义是:“任何节点最多只允许两个子节点”。称为左子节点和右子节点。二叉搜索树可提供对数时间的元素插入和访问。二叉搜索树的节点放置规则是:任何节点的键值一定大于其左子树中的每一个节点的键值,并小于其右子树中每一个节点的键值。因此,从根节点一直往左走,直至无左路可走,即得最小元素;从根节点一直往右走,直至无右路可走,即得最大元素。

    1.2 平衡二叉搜索树(balanced binary search tree)

    也许因为输入值不够随机,也许因为经过某些插入或删除操作,二叉搜索树可能会失去平衡,造成搜寻效率低落的情况。

    所谓树形平衡与否,并没有一个绝对的测量标准。“平衡”的大致意义是:没有任何一个节点过深(深度过大)。不同的平衡条件,造就出不同的效率表现,以及不同的实现复杂度。有数种特殊结构如AVL-tree, RB-tree、AA-tree ,均可实现出平衡二叉搜索树。

    1.3 AVL tree (Adelson-Velskii-Landis tree)

    AVL tree 是一个“加上了额外平衡条件”的二叉搜索树。其平衡条件的建立是为了确保整棵树的深度为O(logN)。直观上的最佳平衡条件是每个节点的左右子树有着相同的高度,但这未免太过苛刻,我们很难插入新元素而又保持这样的平衡条件。AVL tree 于是退而求其次,要求任何节点的左右子树高度相差最多1。这是一个较弱的条件,但仍能够保证“对数深度”平衡状态。

    只要调整“插入点之根节点”路径上,平衡状态被破坏之个节点中最深的那一个,便可使整棵树重新获得平衡。假设该最深节点为X,由于节点最多拥有两个子节点,而所谓“平衡被破坏”意味着X的左右两棵子树的高度相差2,因此我们可以轻易将情况分为四种:

    (1)插入点位于X的左子节点的左子树——左左;

    (2)插入点位于X的左子节点的右子树——左右;

    (3)插入点位于X的右子节点的左子树——右左 ;

    (4)插入点位于X的右子节点的右子树——右右 。

    情况1、4彼此对称,成为外侧插入,可采用单旋转操作调整解决。

    情况2、3彼此对称,成为内侧插入,可采用双旋转调整解决。

     

    一、红黑树的介绍

    先来看下算法导论对R-B Tree的介绍: 红黑树,一种二叉查找树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。

    红黑树,作为一棵二叉查找树,满足二叉查找树的一般性质。下面,来了解下 二叉查找树的一般性质。

    二叉查找树

    二叉查找树,也称有序二叉树(ordered binary tree),或已排序二叉树(sorted binary tree),是指一棵空树或者具有下列性质的二叉树:

    若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 任意节点的左、右子树也分别为二叉查找树。 没有键值相等的节点(no duplicate nodes)。  因为一棵由n个结点随机构造的二叉查找树的高度为lgn,所以顺理成章,二叉查找树的一般操作的执行时间为O(lgn)。但二叉查找树若退化成了一棵具有n个结点的线性链后,则这些操作最坏情况运行时间为O(n)。

    红黑树虽然本质上是一棵二叉查找树,但它在二叉查找树的基础上增加了着色和相关的性质使得红黑树相对平衡,从而保证了红黑树的查找、插入、删除的时间复杂度最坏为O(log n)。

    但它是如何保证一棵n个结点的红黑树的高度始终保持在logn的呢?这就引出了红黑树的5个性质

    (1)每个结点要么是红的要么是黑的。

    (2)根结点是黑的。 

    (3)每个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。 

    (4)如果一个结点是红的,那么它的两个儿子都是黑的。 

    (5)对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点。

    正是红黑树的这5条性质,使一棵n个结点的红黑树始终保持了logn的高度,从而也就解释了上面所说的“红黑树的查找、插入、删除的时间复杂度最坏为O(log n)”这一结论成立的原因。

    (注:上述第3、5点性质中所说的NULL结点,包括wikipedia.算法导论上所认为的叶子结点即为树尾端的NIL指针,或者说NULL结点。然百度百科以及网上一些其它博文直接说的叶结点,则易引起误会,因此叶结点非子结点)

    如下图所示,即是一颗红黑树(下图引自wikipedia:http://t.cn/hgvH1l):

    此图忽略了叶子和根部的父结点。同时,上文中我们所说的 "叶结点" 或"NULL结点",如上图所示,它不包含数据而只充当树在此结束的指示,这些节点在绘图中经常被省略,望看到此文后的读者朋友注意。

    二、树的旋转知识

    当在对红黑树进行插入和删除等操作时,对树做了修改可能会破坏红黑树的性质。为了继续保持红黑树的性质,可以通过对结点进行重新着色,以及对树进行相关的旋转操作,即通过修改树中某些结点的颜色及指针结构,来达到对红黑树进行插入或删除结点等操作后继续保持它的性质或平衡的目的。

    树的旋转分为左旋和右旋,下面借助图来介绍一下左旋和右旋这两种操作。

    1.左旋

    如上图所示,当在某个结点pivot上,做左旋操作时,我们假设它的右孩子y不是NIL[T],pivot可以为任何不是NIL[T]的左子结点。左旋以pivot到Y之间的链为“支轴”进行,它使Y成为该子树的新根,而Y的左孩子b则成为pivot的右孩子。

    //伪代码1:红黑树左旋(右旋类似) - LeftRoate(T, x)   
    // ---------------------------------------------------------------------------------------------------------------------
    y ← x.right                    //y是x的右孩子  
    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  
        then T.root ← y  
    else if x = x.p.left  
        then x.p.left ← y  
    else x.p.right ← y   
    y.left ← x                       //x作为y的左孩子  
    x.p ← y  

    2.右旋
    右旋与左旋差不多,再此不做详细介绍。

    树在经过左旋右旋之后,树的搜索性质保持不变,但树的红黑性质则被破坏了,所以,红黑树插入和删除数据后,需要利用旋转与颜色重涂来重新恢复树的红黑性质。

    至于有些书如《STL源码剖析》有对双旋的描述,其实双旋只是单旋的两次应用,并无新的内容,因此这里就不再介绍了,而且左右旋也是相互对称的,只要理解其中一种旋转就可以了。

    附录4:SGI STL RB_Tree左旋和右旋实现

    三、红黑树的插入

    二叉查找树的插入

    要真正理解红黑树的插入,还得先理解二叉查找树的插入。磨刀不误砍柴工,咱们再来了解一下二叉查找树的插入和红黑树的插入。

    如果要在二叉查找树中插入一个结点,首先要查找到结点要插入的位置,然后进行插入。假设插入的结点为z的话,插入的伪代码如下:

    //伪代码2:二叉树插入 - TREE-INSERT(T, z)
    // ---------------------------------------------------------------------------------------------------------------------  
    y ← NIL  
    x ← T.root  
    while x ≠ NIL  
        do y ←  x  
        if z.key < x.key        // 如果z的key值小,往Tree的左边走,直到叶节点
            then x ← x.left            
        else x ← x.right  
    z.p ← y  
    if y == NIL  //空树 
    then T.root ← z
    else if z.key < y.key then y.left ← z else y.right ← z

    红黑树的插入和插入修复
    现在我们了解了二叉查找树的插入,接下来,咱们便来具体了解下红黑树的插入操作。红黑树的插入相当于在二叉查找树插入的基础上,为了重新恢复平衡,继续做了插入修复操作。

    假设插入的结点为z,红黑树的插入伪代码具体如下所示:

    //伪代码4:红黑树插入和修复 - RB-INSERT(T, z)
    // ---------------------------------------------------------------------------------------------------------------------  
    y ← nil  
    x ← T.root  
    while x ≠ T.nil  
        do y ← x  
        if z.key < x.key  
            then x ← x.left  
        else x ← x.right  
    z.p ← y  
    if y == nil[T]  
        then T.root ← z  
    else if z.key < y.key  
        then y.left ← z  
    else y.right ← z  
    z.left ← T.nil  
    z.right ← T.nil  
    z.color ← RED        // 插入节点默认为红
    RB-INSERT-FIXUP(T, z)        // 插入之后修复

    把上面这段红黑树的插入代码,跟之前看到的二叉查找树的插入代码比较一下可以看出,RB-INSERT(T, z)前面的第1~13行代码基本上就是二叉查找树的插入代码,然后第14~16行代码把z的左孩子和右孩子都赋为叶结点nil,再把z结点着为红色,最后为保证红黑性质在插入操作后依然保持,调用一个辅助程序RB-INSERT-FIXUP来对结点进行重新着色,并旋转。

    (1)如果插入的是根结点,由于原树是空树,此情况只会违反性质2,因此直接把此结点涂为黑色;  

    (2)如果插入的结点的父结点是黑色,由于此不会违反性质2和性质4,红黑树没有被破坏,所以此时什么也不做。

     但当遇到下述3种情况时又该如何调整呢?

    (3)● 插入修复情况1:如果当前结点z的父结点是红色且祖父结点的另一个子结点(叔叔结点)是红色

    (4)● 插入修复情况2:当前节点z的父节点是红色,叔叔节点是黑色,当前节点是其父节点的右子

    (5)● 插入修复情况3:当前节点z的父节点是红色,叔叔节点是黑色,当前节点是其父节点的左子

    答案就是根据红黑树插入代码RB-INSERT(T, z)最后一行调用的RB-INSERT-FIXUP(T, z)函数所示的步骤进行操作,具体如下所示:

    //伪代码4:红黑树插入和修复 - RB-INSERT-FIXUP(T, z)  
    // ---------------------------------------------------------------------------------------------------------------------
    while z.p.color == RED  // 只有z的父节点为Red才做修复;当z的父节点为黑色时,属于情况(2)
        do if z.p == z.p.p.left    // start_flag1,这里只考虑父节点为祖父节点的左孩子
            then y ← z.p.p.right        // y 指向当前节点z的祖父节点的另一个节点(叔叔节点)
            if y.color == RED            // 如果 叔叔节点 为 Red(执行if分支,结果:从修改情况1转到修复情况2)
                then z.p.color ← BLACK          
                y.color ← BLACK                 
                z.p.p.color ← RED               
                z ← z.p.p                          
            else if z == z.p.right  // 当前节点z是其父节点的右子(执行if分支,左旋,结果:从插入修复情况2转换成了插入修复情况3)
                then z ← z.p                     
                LEFT-ROTATE(T, z)            
            z.p.color ← BLACK                  
            z.p.p.color ← RED                   
            RIGHT-ROTATE(T, z.p.p)         
        else (same as then clause with "right" and "left" exchanged)   // // end_flag1,父节点为祖父节点的右孩子 情况类似
    T.root.color ← BLACK  

    下面,咱们来分别处理上述3种插入修复情况。

    插入修复情况1:当前结点的父结点是红色,祖父结点的另一个子结点(叔叔结点)是红色。 如下伪代码所示:

    while z.p.color == RED  
        do if z.p == z.p.p.left  
            then y ← z.p.p.right  
            if y.color == RED  

    此时父结点的父结点一定存在,否则插入前就已不是红黑树(因为,如果z的父节点的父节点不存在,则z的父节点为Root,而z的父节点又为Red,违反性质2.)。与此同时,又分为父结点是祖父结点的左孩子还是右孩子,根据对称性,我们只要解开一个方向就可以了。这里只考虑父结点为祖父左孩子的情况,如下图所示。

    对此,我们的解决策略是:将当前节点的父节点和叔叔节点涂黑,祖父结点涂红,把当前结点指向祖父节点,从新的当前节点重新开始算法。即如下代码所示:

    then z.p.color ← BLACK             
    y.color ← BLACK                
    z.p.p.color ← RED                
    z ← z.p.p                          

    所以,变化后如下图所示:

    于是,插入修复情况1转换成了插入修复情况2

    插入修复情况2:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的右子 此时,解决对策是:当前节点的父节点做为新的当前节点,以新当前节点为支点左旋。即如下代码所示:

    else if z == z.p.right  
        then z ← z.p                 
        LEFT-ROTATE(T, z)      

    所以红黑树由之前的:

    变化成:

    从而插入修复情况2转换成了插入修复情况3

    插入修复情况3:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的左孩子  解决对策是:父节点变为黑色,祖父节点变为红色,在祖父节点为支点右旋,操作代码为:

    z.p.color ← BLACK                 
    z.p.p.color ← RED                 
    RIGHT-ROTATE(T, z.p.p)     

    最后,把根结点涂为黑色,整棵红黑树便重新恢复了平衡。所以红黑树由之前的:

    变化成:

    「回顾:经过上面情况3、情况4、情况5等3种插入修复情况的操作示意图,读者自会发现,后面的情况4、情况5都是针对情况3插入节点4以后,进行的一系列插入修复情况操作,不过,指向当前节点N指针一直在变化。所以,你可以想当然的认为:整个下来,情况3、4、5就是一个完整的插入修复情况的操作流程」

    注:RB_Tree的插入,同时也完成了RB_Tree的构造。

    附录3:SGI STL RB_Tree插入修复实现

    四、红黑树的删除  

    接下来,咱们最后来了解,红黑树的删除操作。

     "我们删除的节点的方法与常规二叉搜索树中删除节点的方法是一样的,如果被删除的节点不是有双非空子女(1个或0个孩子),则直接删除这个节点,用它的唯一子节点顶替它的位置,如果它的子节点是空节点,那就用空节点顶替它的位置,如果它的双子全为非空,我们就把它的直接后继节点(沿着左子树不断右走直至到达第一个右节点为nil的节点;或沿着右子树不断左走直至到达第一个左节点为nil的节点。)内容复制到它的位置,之后以同样的方式删除它的后继节点,它的后继节点不可能是双子非空,因此此传递过程最多只进行一次。”

    二叉查找树的删除

    继续讲解之前,补充说明下二叉树结点删除的几种情况,待删除的节点按照儿子的个数可以分为三种:

    (1)没有儿子,即为叶结点。直接把父结点的对应儿子指针设为NULL,删除儿子结点就OK了。

    (2)只有一个儿子。那么把父结点的相应儿子指针指向儿子的独生子,删除儿子结点也OK了。

    (3)有两个儿子。这是最麻烦的情况,因为你删除节点之后,还要保证满足搜索二叉树的结构。其实也比较容易,我们可以选择左儿子中的最大元素或者右儿子中的最小元素放到待删除节点的位置,就可以保证结构的不变。当然,你要记得调整子树,毕竟又出现了节点删除(当删除的节点不是叶节点,需要调整其子树)。习惯上大家选择左儿子中的最大元素,其实选择右儿子的最小元素也一样,没有任何差别,只是人们习惯从左向右。这里咱们也选择左儿子的最大元素,将它放到待删结点的位置。左儿子的最大元素其实很好找,只要顺着左儿子不断的去搜索右子树就可以了,直到找到一个没有右子树的结点。那就是最大的了。 二叉查找树的删除代码如下所示:

    //伪代码5:二叉树删除 - TREE-DELETE(T, z)  
    // ---------------------------------------------------------------------------------------------------------------------
     1  if left[z] = NIL or right[z] = NIL  
     2      then y ← z      // z节点没有儿子或者只有一个儿子,y指向z节点
     3      else y ← TREE-SUCCESSOR(z)     // z节点有两个儿子,y指向z的后继节点(这里选择左儿子的最大元素)  
     4  if left[y] ≠ NIL  
     5      then x ← left[y]  
     6      else x ← right[y]  
     7  if x ≠ NIL      // 如果y为叶节点(没有儿子),或者y指向z的后继节点并且此后继节点没有左儿子,则x为nil;否则(z节点有且只有一个儿子,或者z的后继节点有左儿子),x不为nil.
     8      then p[x] ← p[y]   // x的父节点设为y的父节点(1.修改x节点的父指针,指向y的父节点)
     9  if p[y] = NIL  
    10      then root[T] ← x  
    11      else if y = left[p[y]]      // y 是其父节点的左子树
    12              then left[p[y]] ← x   // 重设指针,将y的父节点的左指针指向x(2.也即,令x节点为y的父节点的左儿子)
    13              else right[p[y]] ← x  
    14  if y ≠ z  // y指向z的后继节点
    15      then key[z] ← key[y]  // 把y的内容复制到被删除节点z的位置,z的原来的内容被替换(也即删除了z),而原来的y位置的节点顶替了z。
    16           copy y's satellite data into z  
    17  return y      // 返回(顶替z节点的)y节点的指针

    红黑树的删除和删除修复

    OK,回到红黑树上来,红黑树结点删除的算法实现是:

    RB-DELETE(T, z) 单纯删除结点的总操作:

    //伪代码6:红黑树删除与修复 - RB-DELETE(T, Z)
    // ---------------------------------------------------------------------------------------------------------------------
     1 if left[z] = nil[T] or right[z] = nil[T]    
     2    then y ← z    
     3    else y ← TREE-SUCCESSOR(z)    
     4 if left[y] ≠ nil[T]    
     5    then x ← left[y]    
     6    else x ← right[y]    
     7 p[x] ← p[y]    // 1. 红黑树,nil节点也要有parent指针指向一个节点,故不用判x是否为nil(nil,尾端节点,黑色)
     8 if p[y] = nil[T]    
     9    then root[T] ← x    
    10    else if y = left[p[y]]    
    11            then left[p[y]] ← x    
    12            else right[p[y]] ← x    
    13 if y ≠ z    
    14    then key[z] ← key[y]    
    15         copy y's satellite data into z    
    //2. 以下为不同之处 ----------------------------------------------- 16 if color[y] = BLACK 17 then RB-DELETE-FIXUP(T, x) // 修复红黑树(根据顶替节点y(已用来顶替被删除的节点z)的颜色,判断是否需要修复工作.因为顶替节点在原来位置被删除,修复工作从顶替节点的位置开始) 18 return y

    参见附录1:RB_Tree删除节点解释。

    附录1:RB_Tree删除节点

    相对于红黑树插入操作,删除操作复杂的多。

    第一:先看最简单情况,即删除红色节点。删除红色节点,不影响红黑树平衡性质,如图:

     

    只需要删除红色节点,不需要进行调整,因为不影响红黑树的性质。  黑色节点没有增多也没有减少。

    注意:以下几种单支情况在平衡的红黑树中不可能出现。

    因为上述的情况,红黑树处于不平衡状态。(破坏性质“对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点”)

    注:

    a:(1)、(4)号图中通过x左儿子到达nil的黑色节点数,和通过x-y在到达nil的黑色节点数不一样;

    b:(2)、(3)号图中通过x右儿子到达nil的黑色节点数,和通过x-y在到达nil的黑色节点数不一样;

    所以,平衡状态下红黑树要么单支黑-红,要么有两个子节点
     

    第二:删除单支黑节点

             

    此种情况被包含在“第三”中,详见“第三”分析

    第三:若删除节点有左右两个儿子,即左右子树,需要按照二叉搜索树的删除规律,从右子树中找最小的替换删除节点(该节点至多有一个右子树,

    无左子树),我们将该节点记为y, 将删除节点记为z,将y的右儿子记为x(可能为空)。

    删除规则:用y替换z(替换的结果是,y和z的颜色也会被替换),交换y与z颜色(交换y和z的颜色,其实也就是取消刚才的颜色替换,也即y和z位置原来的颜色不变),同时y = z,改变y的指向,让y指向最终删除节点。

    为了便于理解,可以先这样假设:将y与z的数据交换,但颜色不交换,这样,实际相当于将删除转移到了y节点,而z处保持原先状态(处于平衡)。此时可以完全不用理会z节点,直接删除y节点即可。因为y最多只有一个右子树,无左子树,这便转移到了“第二”。针对“第二”,在删除y节点的时候,就要考虑到下面几种情况:

      1. 若y为红色,则这种情况如上述“第一”所述,并不影响平衡性。(null节点视为黑色);

      2. 若y为黑色,则删除y后,x替换了y的位置,这样x子树相对于兄弟节点w为根的子树少了一个黑节点,影响平衡,需要进行下面调整:

    下面的调整工作(x已经替换到了y的位置)就是将x子树中找一合适红色节点,将其置黑,使得x子树与w子树达到平衡。

         a、若x为红色,直接将x置为黑色,即可达到平衡;

        

          b、若x为黑色,则分下列几种情况。

          

          情况1:x的兄弟w为红色,则w的儿子必然全黑,w父亲p也为黑。

          

           改变p与w的颜色,同时对p做一次左旋,这样就将情况1转变为情况2,3,4的一种。

          情况2:x的兄弟w为黑色,w的两个儿子也都是黑的(否则,则相应进入了情况3、情况4),x与w的父亲颜色可红可黑。

           

           因为x子树相对于其兄弟w子树少一个黑色节点,可以将w置为红色,这样,x子树与w子树黑色节点一致,保持了平衡。

          new x为x与w的父亲。new x相对于它的兄弟节点new w少一个黑色节点。如果new x为红色,则将new x置为黑,则整棵树平衡。否则,

          情况2转换为情况1,3,4  情况2转变为情况1,2,3,4.

          情况3:w为黑色,w左孩子红色,右孩子黑色。

          

           交换w与左孩子的颜色,对w进行右旋。转换为情况4

           情况4:w为黑色,右孩子为红色。

           

            交换w与父亲p颜色,同时对p做左旋。这样左边缺失的黑色就补回来了,同时,将w的右儿子置黑,这样左右都达到平衡。

    上述四种情况,每一次旋转(左旋、右旋)都伴随着颜色的交换。

    个人认为这四种状况比较难以理解,总结了一下:

      情况2:最好理解,减少右子树的一个黑色节点,使x与w平衡,将不平衡点上移至x与w的父亲。进行下一轮迭代。

      情况1:如果w为红色,通过旋转,转成成情况2,3,4进行处理。而情况3转换为情况4进行处理。也就是说,情况4是最接近最终解的情况。

      情况4:右儿子是红色节点,那么将缺失的黑色交给右儿子,通过旋转,达到平衡。

    附录2:SGI STL RB_Tree删除实现:

    // RB_Tree 删除
    inline _Rb_tree_node_base*
    _Rb_tree_rebalance_for_erase(_Rb_tree_node_base* __z,
                                 _Rb_tree_node_base*& __root,
                                 _Rb_tree_node_base*& __leftmost,
                                 _Rb_tree_node_base*& __rightmost)
    {
      _Rb_tree_node_base* __y = __z;
      _Rb_tree_node_base* __x = 0;
      _Rb_tree_node_base* __x_parent = 0;
      if (__y->_M_left == 0)     // __z has at most one non-null child. y == z.
        __x = __y->_M_right;     // __x might be null.
      else
        if (__y->_M_right == 0)  // __z has exactly one non-null child. y == z.
          __x = __y->_M_left;    // __x is not null.
        else {                   // __z has two non-null children.  Set __y to
          __y = __y->_M_right;   //   __z's successor.  __x might be null.
          while (__y->_M_left != 0)
            __y = __y->_M_left;
          __x = __y->_M_right;
        }
      if (__y != __z) {          // relink y in place of z.  y is z's successor
        __z->_M_left->_M_parent = __y; 
        __y->_M_left = __z->_M_left;
        if (__y != __z->_M_right) {
          __x_parent = __y->_M_parent;
          if (__x) __x->_M_parent = __y->_M_parent;
          __y->_M_parent->_M_left = __x;      // __y must be a child of _M_left
          __y->_M_right = __z->_M_right;
          __z->_M_right->_M_parent = __y;
        }
        else
          __x_parent = __y;  
        if (__root == __z)
          __root = __y;
        else if (__z->_M_parent->_M_left == __z)
          __z->_M_parent->_M_left = __y;
        else 
          __z->_M_parent->_M_right = __y;
        __y->_M_parent = __z->_M_parent;
        __STD::swap(__y->_M_color, __z->_M_color);
        __y = __z;
        // __y now points to node to be actually deleted
      }
      else {                        // __y == __z
        __x_parent = __y->_M_parent;
        if (__x) __x->_M_parent = __y->_M_parent;   
        if (__root == __z)
          __root = __x;
        else 
          if (__z->_M_parent->_M_left == __z)
            __z->_M_parent->_M_left = __x;
          else
            __z->_M_parent->_M_right = __x;
        if (__leftmost == __z) 
          if (__z->_M_right == 0)        // __z->_M_left must be null also
            __leftmost = __z->_M_parent;
        // makes __leftmost == _M_header if __z == __root
          else
            __leftmost = _Rb_tree_node_base::_S_minimum(__x);
        if (__rightmost == __z)  
          if (__z->_M_left == 0)         // __z->_M_right must be null also
            __rightmost = __z->_M_parent;  
        // makes __rightmost == _M_header if __z == __root
          else                      // __x == __z->_M_left
            __rightmost = _Rb_tree_node_base::_S_maximum(__x);
      }
      /********* 以上完成二叉树删除操作,下面进行RB_Tree修复 *********/
      if (__y->_M_color != _S_rb_tree_red) {    // 只需考虑__y为黑色的情况(若__y为红色,直接删除,__x直接顶替__y原来的位置)
        while (__x != __root && (__x == 0 || __x->_M_color == _S_rb_tree_black))    //__x为nil(nil节点为黑色)
          if (__x == __x_parent->_M_left) {        // 只考虑__x是左儿子的情况(__x为右儿子情况类似)
            _Rb_tree_node_base* __w = __x_parent->_M_right;        // __x的兄弟节点w
            if (__w->_M_color == _S_rb_tree_red) {        // case1:兄弟节点w为红色
              __w->_M_color = _S_rb_tree_black;
              __x_parent->_M_color = _S_rb_tree_red;
              _Rb_tree_rotate_left(__x_parent, __root);        // 左旋
              __w = __x_parent->_M_right;
            }
            if ((__w->_M_left == 0 || 
                 __w->_M_left->_M_color == _S_rb_tree_black) &&
                (__w->_M_right == 0 || 
                 __w->_M_right->_M_color == _S_rb_tree_black)) {    // case2:兄弟节点w为黑色,并且其两个儿子都是黑色(或者都为nil)
              __w->_M_color = _S_rb_tree_red;
              __x = __x_parent;
              __x_parent = __x_parent->_M_parent;
            } else {        
              if (__w->_M_right == 0 || 
                  __w->_M_right->_M_color == _S_rb_tree_black) {    // case3:兄弟节点w为黑色,并且其右儿子为黑(也即左儿子为红)
                if (__w->_M_left) __w->_M_left->_M_color = _S_rb_tree_black;
                __w->_M_color = _S_rb_tree_red;
                _Rb_tree_rotate_right(__w, __root);
                __w = __x_parent->_M_right;
              }
              // case4:剩下最后一种情况,也即兄弟节点w为黑色,并且其右儿子为红(左儿子可红可黑)
              __w->_M_color = __x_parent->_M_color;
              __x_parent->_M_color = _S_rb_tree_black;
              if (__w->_M_right) __w->_M_right->_M_color = _S_rb_tree_black;
              _Rb_tree_rotate_left(__x_parent, __root);
              break;
            }
          } else {
              // __x为右儿子情况,same as above, with _M_right <-> _M_left.
          }
    
        if (__x) __x->_M_color = _S_rb_tree_black;    // __x为根节点,或者为红色节点,直接染黑便符合RB_Tree性质
      }
      return __y;
    }

    附录3:SGI STL RB_Tree插入修复实现:

    // RB_Tree插入修复
    // 插入节点__x初始置为红色
    inline void 
    _Rb_tree_rebalance(_Rb_tree_node_base* __x, _Rb_tree_node_base*& __root)
    {
      __x->_M_color = _S_rb_tree_red;
      while (__x != __root && __x->_M_parent->_M_color == _S_rb_tree_red) {        // 父节点p为红才需要调整(父节点p为黑的话,__x直接插入,不改变RB_Tree性质)
        if (__x->_M_parent == __x->_M_parent->_M_parent->_M_left) {        // 父节点p为祖父节点g的左儿子    
          _Rb_tree_node_base* __y = __x->_M_parent->_M_parent->_M_right;    // __y指向父节点的兄弟节点,也即叔叔节点
          if (__y && __y->_M_color == _S_rb_tree_red) {        // case1:叔叔节点__y为红色,执行分支,结果:从case1转到case2
            __x->_M_parent->_M_color = _S_rb_tree_black;
            __y->_M_color = _S_rb_tree_black;
            __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
            __x = __x->_M_parent->_M_parent;
          }
          else {
            if (__x == __x->_M_parent->_M_right) {        // case2;叔叔节点__y为黑色,当前节点是父节点p的右儿子,执行分支,结果:从case2转到case3
              __x = __x->_M_parent;
              _Rb_tree_rotate_left(__x, __root);
            }
            // case3:剩下最后一种情况,叔叔节点__y为黑色,当前节点是父节点p的左儿子,执行分支,结果:RB_Tree重新恢复了平衡
            __x->_M_parent->_M_color = _S_rb_tree_black;
            __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
            _Rb_tree_rotate_right(__x->_M_parent->_M_parent, __root);
          }
        }
        else {
          // 父节点p为祖父节点g的右儿子    
          _Rb_tree_node_base* __y = __x->_M_parent->_M_parent->_M_left;
          if (__y && __y->_M_color == _S_rb_tree_red) {
            __x->_M_parent->_M_color = _S_rb_tree_black;
            __y->_M_color = _S_rb_tree_black;
            __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
            __x = __x->_M_parent->_M_parent;
          }
          else {
            if (__x == __x->_M_parent->_M_left) {
              __x = __x->_M_parent;
              _Rb_tree_rotate_right(__x, __root);
            }
            __x->_M_parent->_M_color = _S_rb_tree_black;
            __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
            _Rb_tree_rotate_left(__x->_M_parent->_M_parent, __root);
          }
        }
      }
      __root->_M_color = _S_rb_tree_black;    // 如果插入的是根结点,由于原树是空树,此情况只会违反性质2,因此直接把此结点涂为黑色
    }

    附录4:SGI STL RB_Tree左旋和右旋实现

    inline void 
    _Rb_tree_rotate_left(_Rb_tree_node_base* __x, _Rb_tree_node_base*& __root)
    {
      _Rb_tree_node_base* __y = __x->_M_right;
      __x->_M_right = __y->_M_left;
      if (__y->_M_left !=0)
        __y->_M_left->_M_parent = __x;
      __y->_M_parent = __x->_M_parent;
    
      if (__x == __root)
        __root = __y;
      else if (__x == __x->_M_parent->_M_left)
        __x->_M_parent->_M_left = __y;
      else
        __x->_M_parent->_M_right = __y;
      __y->_M_left = __x;
      __x->_M_parent = __y;
    }
    
    inline void 
    _Rb_tree_rotate_right(_Rb_tree_node_base* __x, _Rb_tree_node_base*& __root)
    {
      _Rb_tree_node_base* __y = __x->_M_left;
      __x->_M_left = __y->_M_right;
      if (__y->_M_right != 0)
        __y->_M_right->_M_parent = __x;
      __y->_M_parent = __x->_M_parent;
    
      if (__x == __root)
        __root = __y;
      else if (__x == __x->_M_parent->_M_right)
        __x->_M_parent->_M_right = __y;
      else
        __x->_M_parent->_M_left = __y;
      __y->_M_right = __x;
      __x->_M_parent = __y;
    }

    【参考1】

    1、教你透彻了解红黑树

    2、红黑树算法的实现与剖析

    3、红黑树的c源码实现与剖析

    4、一步一图一代码,R-B Tree

    5、红黑树插入和删除结点的全程演示

    6、红黑树的c++完整实现源码

    【参考2】红黑树(删除)

  • 相关阅读:
    UIApplication sharedApplication详细解释-IOS
    CocoaPods版本升级
    iOS更改状态栏颜色__
    代码实现UIPickerView
    在UINavigation上添加UISearchBar
    runtime实际应用---
    runtime实际应用
    runtime 运行时机制 完全解读(二)
    RunTime机制--原理(一)
    同步推笔试坑录
  • 原文地址:https://www.cnblogs.com/yyxt/p/4983967.html
Copyright © 2011-2022 走看看