红黑树 插入和删除详解 -- 草稿

 

- 这只是一篇草稿,还没排版,以前一直想给本身写一个文章的模板,一直还没写,因此估计写的比较慢,因此先放出来文章,后面再来填坑吧。   这篇文章的初衷是看到面试问题有问到红黑树,甚至有手撕红黑树代码的?可怕,红黑树的讨论状况不少,一不当心就绕进去,真让人犯难,并且网上的文章各有各的说法,很多人理解甚至是错误的。java

 

-  红黑树:红黑树是一种二叉平衡树,二叉查找树,它牛逼之处就在于它足够的平衡,能够达到高度至多2lg(n+1),因此在java中的treemap和c++ set, multiset, map, multimap就使用的红黑树。 c++

- 红黑树的性质:1. 结点分为红色和黑色两种  2.根节点是黑色的  3.每一个叶子结点(nil)是黑色的(就是空表明了黑色) 4.不存在父子都是红色的状况(连续两个红色) 5.任意孩子到根节点的路径上的黑色数量都是相等的(important)面试

- 本文将解释 如下内容:算法

 1.  红黑树的插入(简单的插入)与调整(调整平衡)。 ide

2. 红黑树的删除(也带有调整,使其便于调整平衡)与调整(调整平衡)。spa

 

### 插入3d

#### 插入方式:code

  先考虑插入什么颜色的结点,这里须要说明颜色只是咱们用于保证平衡的一种方法,与数据无关。 由于插入黑色结点除非在根节点,不然必定会致使一个分支上的全部子分支都多了一个黑,如此不知足性质5(简称 路黑同)。因此咱们插入红色。插入方式就是普通的二叉排序树的插入方式。就这么简单。对象

 

#### 调整:blog

  由于插入的调整相对简单,咱们能够将全部的状况列举出来而后讨论,列举的依据是:由于插入红色的结点不会对插入节点的子节点形成任何影响(即符合性质1-5,当我在作任何断言时,最好本身试试),因此仅需考虑插入结点的祖先的状况。

(图实在太多了,因此暂时手画了sorry) 

 

具体而言咱们要作的就是讨论一段区间,它的全部状况必须知足调整后对祖先和子孙都而言都仍然平衡,由于原来的两段都是平衡的,因此只需保证在讨论区间中是的任意左右子树知足性质,而且不改变原来和祖先和子树的接口,那么就必定能维持性质不变。

 

 图中从左到右,分别是红色,黑色,任意颜色,附加黑(意思是除了本来结点带有的颜色还附加一层黑色,用处后面用到的时候再讲),

图中没有画出儿子结点不表明它儿子就是nil,若是有叶子结点会标nil的。

 下图为全部状况

 

状况1:单个结点,没有父节点。

 

 

 

调整方法:红色变成黑色,性质2(简称 根黑),

状况2:  父节点是黑色,这样原本就是平衡的,可是因为讨论的结点儿子多是红色,能够将这种状况转化成讨论它的儿子的状况,它的儿子的父节点是红色,对应接下来正好要讨论的状况3,因此状况2应该算状况3的一种子状况。

 

状况3:父节点是红色

分为两种状况, 一个是父的右孩子,一个是父的左孩子。

 

 

 

 

    

 

对于状况3.1.1  只需将父节点和叔父结点变成黑色,而后祖父结点变成红色,就能够维持两个分支的黑色结点一致,而且没有两个红色,能够对照知足了全部性质,这就算是恢复好了,但仅仅是针对图中最上方的结点的子树,若以调整后最上方的红色结点做为讨论对象,讨论他的父结点状况,就又能够递归的分类讨论进行恢复,实际就是算法中的递归调用。

 

对于状况3.1.2 方式相似3.1.1

 

对于状况3.1.3, 能够先以红-红中的父节点进行左旋转,而后和状况3.2.3同样了,只需再以它为中心右旋一次,并如图变换颜色便可,最重要的依据就是分支的黑色要一致。因此调整后整棵树就恢复好了。由于讨论区间分别和子树以及子树链接的部分的红色维持不变,因此不会影响到本来的祖先和子孙的平衡状况。

 

 

 

对于其余状况,能够相似上面的四种状况进行操做,再也不一一介绍,为了和教科书上(算法导论)保持一致,而且简化状况,咱们须要将多种状况进行合并。合并的最后结果只剩下三种状况。如图中棕色的标号, (要记就记如下的结果)

①表明了第一种状况,这里把只有一个红色结点的状况归类为了父节点为黑色,实际上也是这么定义的,根节点的父节点为nil,nil为黑色,这样就进行了统一。

①概括了状况:插入节点的父和叔节点是红色时,把他们变黑,再把爷爷变红,递归调用爷爷做为参数继续算法,

 

 

②概括了状况:插入节点的父是红色uncle是黑色时,若是本身是父的右结点,先左旋到状况③

 

③概括了状况:本身是父的左结点)而后再把fauncle变成黑色,grandparent变成红色再以父亲为参数右旋

 

 

 

 对应的还有①', ②',③',它和①, ②,③的区别就在于讨论的结点是在左子树仍是右子树,操做方法是对称的,因此恢复算法一共有六种状况,可是三种也对。

 附上代码:

#插入方法
RB-INSERT(T,z): #z就是插入的结点也是fix时当前讨论的结点
    y = T.nil
    x = T.root
    while x != T.nil
        y = x
        if z.key < x.key
            x = x.left
        else x = x.right
    z.p = y
    if y == T.nil
        T.root = z
    else if z.key  < y.key
        y.left = z
    else y.right = z
    z.left  = T.nil
    z.right = T.nil
    z.color = RED
    RB-INSERT-FIXUP(T,z)
View Code

 

 

View Code

 

---

 

### 删除:

删除状况讨论若是按照以前的将全部状况都讨论出来,那会很累,而且会绕进去, 因此咱们仍是根据现有的算法,来讨论分类

##### 删除的方式:根据状况删除时就作一些调整。

##### 主要的讨论分类方式:将删除分类为1.若是删除的结点只有一个孩子(不考虑nil),又可继续讨论下去,那个孩子是左孩子仍是右孩子,

2.若是删除的结点有两个孩子,一样也可详细讨论  ,固然你也能够说还有一种状况,若是删除的结点一个孩子都没有,那种状况只用单纯的删除,删除过程当中没有进行额外的操做,全部删除操做都要进行删除步骤,因此这里不进行讨论,注意到咱们仅讨论了删除节点为根的子树状况,而没有讨论它的祖先结点,不是由于不影响,而是在删除过程当中仅须要对以上几种状况分开讨论以进行额外的操做,便于删除恢复。 

##### 删除的具体状况: 删除过程咱们的主要目的是保证经过一些方法让整棵树仍然保持平衡(加附加黑的方式),而后在恢复的过程当中解决附加黑的问题,如下讨论中a为待删除结点

删除掉a,将另外一个结点做为替换,由于 原来的a结点可能就是黑色的,这样a的子树就少了一个黑色,因此将b涂成黑色。咱们发现这种状况就再也不须要调整了。已经知足了要求的各类性质,不信能够试试。

 

若是有两个孩子的话,而且 a(待删除结点)的儿子结点的左儿子为nil,就说明a是c最小的后继,删除a后,将c移动到a的位置,固然也能够用a的左子树中最大的数即a的前驱来替换他。除此以外,这种状况下,c必须和a的颜色相同,目的是使得改动最小,可是当a是黑色的时候,因为c,d根据性质必有一黑,当c是黑时,c替换后,c的分支相对于原来,少了一个黑色的结点,这时候就须要多加一重黑色来保证平衡

 

 

若是有两个孩子,而且a的儿子结点的儿子结点不为空,那么,经过找到左儿子的最小值,即dfs一直找最左儿子为nil的d,将它和a进行交换,而后e代替nil,同理仍是d代替a的颜色,这时a为黑,且d为黑时就会不平衡,这时候如同状况2也须要给e多加一层黑色。

 

算法中①的状况由于左为nil和右为nil操做虽然对称,可是不同,因此通常分红四种状况

 

 

#####  删除恢复,

最激动人心的时刻,删除恢复,不知是谁想的附加黑色这种骚操做,通过了上面的删除,咱们如今只须要考虑恢复的状况只有如何将附加黑状况进行处理,基本思路是只考虑附加黑结点的子树之外的结点,由于算法试图将附加黑不断向树根传递,来达到在某一部消除的方式来恢复。

具体的讨论状况分为 附加黑结点的兄弟是1.红,2.黑(有分为三种状况: 1。兄弟的儿子为全黑 2.左红右黑 3.左any右红 )本身画画是否讨论彻底了。 这样算一共4种子状况以下: 

状况1:

这种状况是 附加黑的兄弟是红色,进行左旋,对应了第二大类的讨论。

 

状况2:

兄弟为黑,而且兄弟儿子全黑,这时候将附加黑向上传递,这样右边就多了一重黑色,因此将c改为红色,b的子树就知足要求了,而后继续以b为结点进行递归

 

兄弟为黑而且左红右黑,经过以c为中心右旋再交换dc颜色就达到了状况4

状况4: 

 

 

经过以b为中心左旋,这时咱们发现左边和右边肯定的黑分别为2和1,原来是1和1,因此须要给右边增长一层黑,经过将附加黑给b让b称为黑色,同时使得e为黑色,这样就知足了左2黑右2黑,可是要保持原来的黑色数为1,因此就将c改为原来b位置应有的任意

 

 

- 以上四种状况只考虑了a(带附加黑的点)是左儿子的状况, 一样右儿子是对称,因此和插入同样,4种基本状况实际是2x4种详细状况

 

如下为删除的伪代码:

RB-DELETE(T,z):
    y = z
    y-origin-color = y.color
    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-MINIMUN(z,right)
        y-original-color = y.color
        x = y.right
        if y.p == z
            x.p = y
        else RB-TRANPLANT(T,y,y.right)
            y.right = z.right   
            y.right.p = y
        RB-TRANSPLANT(T,z,y)
        y.left = z.left
        y.left.p = y
        y.color = z.color
        if y-original-color ==BLACK
            RB-DELETE-FIXUP(T,x)
View Code
 

 

RB-DELETE-FIXUP(T,x)
    while x!=T.root and x.color ==BLACK:
        if x== x.p.left
            w = x.p.right
            if w.color ==RED                                                     # case 1
                w.color = BLACK
                x.p.color = RED
                LEFT-ROTATE(T,x.p)
                w  = x.p.right
            if w.left.color ==BLACK and w.right.color ==BLACK 
                w.color = RED                                                    #case 2
                x = x.p
            else if w.right.color ==BLACK
                w.left.color =  BLACK                                         #case 3
                w.color = RED
                RIGHT-ROTATE(T,w)
                w = x.p.right
            w.color = x.p.color                                                 #case 4
            x.p.color = BLACK
            w.right.color = BLACK
            LEFT-ROTATE(T, x.p) 
            x = T.root         
        else  (same as then clause with "right" and left exchanged )   #symetric case
    x.color = BLACK                
View Code
相关文章
相关标签/搜索