红黑树

问题

  • 什么是红黑树?
  • 红黑树怎么自平衡?
  • 什么时候左旋和右旋?
  • 插入或删除操作破坏了熟的平衡该怎么处理?

特征

红黑树是自平衡的二叉查找树. 除了具备二叉查找树的特征之外,还具有以下特征:

  • 性质1: 节点要么是红色要么是黑色
  • 性质2: 根节点是黑色
  • 性质3: 每个叶子的节点都是黑色的空节点
  • 性质4: 每个红色节点的两个子节点都是黑色
  • 性质5: 从任意节点到其每个叶子的所有路径都包含相同的黑色节点

根据性质5 ==> 如果一个节点存在黑色子节点,那么该节点肯定有两个节点

正是红黑树的五条性质,使得一棵n个节点的红黑树,始终保持了lg(n)的高度。
==> 红黑树的查找,插入,删除的时间复杂度最坏为O(log n).
红黑树

红黑树性质修复

红黑树的性质会在节点的插入,删除的时候,受到影响. 我们可以通过 旋转,着色两种方式来进行修复。旋转分为分为左旋和右旋。

  • 左旋

左旋

如上图, 当在节点privot上进行左旋操作的时候,我们假设它的右孩子Y不为null,privot可以为任何不适Null节点的左孩子节点.旋转过程如下:
以privot节点到Y节点之间的链为支轴进行,它使Y称为该孩子树新的根,而Y的左孩子B称为privot的右孩子。
结果如上右侧图。

  • 右旋

在这里插入图片描述

和左旋类似,旋转过程如下:
以privot节点和Y节点的链为支轴,将Y节点变为当前子树的新根,privot节点作为Y节点的右子树,C节点作为Y节点的左子树。
结果如上右侧图。

注意: 对于树的旋转,能保持不变的只有原树的搜索性质,而原树的红黑性质则不能保持,这时候就需要利用着色方式来进行修复了,具体的修复场景如下:

  • 场景1: 当前节点的父节点是红色,并且叔叔节点是红色。==>父节点涂黑,叔叔节点涂黑,祖父节点涂红
  • 场景2: 当前节点的父节点是红色,叔叔是黑色,当前节点是父节点的右子树。==>当前节点的父节点作为新的当前节点,以新的当前节点左旋。
  • 场景3: 当前节点的父节点是红色,叔叔节点是黑色,当前节点是父节点的左子树. ==>父节点变为黑色,祖父节点变为红色,以祖父节点为支点右旋。

其实呢还有两种场景,不过这两种太简单了,这里简单提一嘴。

  • 红黑树为空树,直接插入当前节点,节点涂为黑色就好了。
  • 插入节点的父节点是黑色,直接插入当前节点,因为红色节点不会影响红黑树的性质。

红黑树的插入过程

接下来以插入过程解释下上面提及的3个场景。

  • 针对场景1:

在这里插入图片描述
使用策略: 父节点涂黑,叔叔节点涂黑,祖父节点涂红,变成下图:
在这里插入图片描述

这样就是变成了场景2:当前节点的父节点是红色,叔叔是黑色,当前节点是父节点的右子树.

  • 针对场景2,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MYjpcEWY-1591504960692)在这里插入图片描述

使用策略:当前节点的父节点作为新的当前节点,以新的当前节点左旋。

在这里插入图片描述

这样就变成场景三: 当前节点的父节点是红色,叔叔节点是黑色,当前节点是父节点的左子树

  • 针对场景3:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PUgNl60n-1591504960696)在这里插入图片描述

使用策略: 父节点变为黑色,祖父节点变为红色,以祖父节点为支点右旋。

在这里插入图片描述

到此为止就是一颗完整的红黑树了。

红黑树的删除过程

红黑树的删除节点和常规二叉树删除方法是一样的.

  • 如果它的子结点都是空结点,那就用空结点顶替它的位置
  • 如果被删除的结点只有一个子节点,则直接删除这个结点
  • 如果它的双子全为非空,我们就把它的直接后继结点内容复制到它的位置,之后以同样的方式删除它的后继结点,它的后继结点不可能是双子非空,因此此传递过程最多只进行一次。

二叉树的删除算法:

if left[z] = NIL or right[z] = NIL
    then y ← z
    else y ← TREE-SUCCESSOR(z)
if left[y] ≠ NIL
    then x ← left[y]
    else x ← right[y]
if x ≠ NIL
    then p[x] ← p[y]
if p[y] = NIL
    then root[T] ← x
    else if y = left[p[y]]
            then left[p[y]] ← x
            else right[p[y]] ← x
if y ≠ z
    then key[z] ← key[y]
         copy y's satellite data into z
return y

红黑树的删除算法:

if left[z] = nil[T] or right[z] = nil[T]  
   then y ← z  
   else y ← TREE-SUCCESSOR(z)  
if left[y] ≠ nil[T]  
   then x ← left[y]  
   else x ← right[y]  
p[x] ← p[y]  
if p[y] = nil[T]  
   then root[T] ← x  
   else if y = left[p[y]]  
           then left[p[y]] ← x  
           else right[p[y]] ← x  
if y ≠ z  
   then key[z] ← key[y]  
        copy y's satellite data into z  
if color[y] = BLACK  
   then RB-DELETE-FIXUP(T, x)  
return y  

在删除结点后,原红黑树的性质可能被改变,如果删除的是红色结点,那么原红黑树的性质依旧保持,此时不用做修正操作,如果删除的结点是黑色结点,原红黑树的性质可能会被改变,我们要对其做修正操作。那么哪些树的性质会发生变化呢,如果删除结点不是树唯一结点,那么删除结点的那一个支的到各叶结点的黑色结点数会发生变化,此时性质5被破坏。如果被删结点的唯一非空子结点是红色,而被删结点的父结点也是红色,那么性质4被破坏。如果被删结点是根结点,而它的唯一非空子结点是红色,则删除后新根结点将变成红色,违背性质2

红黑树修复属性使用的算法:

while x ≠ root[T] and color[x] = BLACK  
    do if x = left[p[x]]  
          then w ← right[p[x]]  
               if color[w] = RED  
                  then color[w] ← BLACK                        ▹  Case 1  
                       color[p[x]] ← RED                       ▹  Case 1  
                       LEFT-ROTATE(T, p[x])                    ▹  Case 1  
                       w ← right[p[x]]                         ▹  Case 1  
               if color[left[w]] = BLACK and color[right[w]] = BLACK  
                  then color[w] ← RED                          ▹  Case 2  
                       x ← p[x]                                ▹  Case 2  
                  else if color[right[w]] = BLACK  
                          then color[left[w]] ← BLACK          ▹  Case 3  
                               color[w] ← RED                  ▹  Case 3  
                               RIGHT-ROTATE(T, w)              ▹  Case 3  
                               w ← right[p[x]]                 ▹  Case 3  
                        color[w] ← color[p[x]]                 ▹  Case 4  
                        color[p[x]] ← BLACK                    ▹  Case 4  
                        color[right[w]] ← BLACK                ▹  Case 4  
                        LEFT-ROTATE(T, p[x])                   ▹  Case 4  
                        x ← root[T]                            ▹  Case 4  
       else (same as then clause with "right" and "left" exchanged)  
color[x] ← BLACK  

上面的修复情况看起来有些复杂,下面我们用一个分析技巧:我们从被删结点后来顶替它的那个结点开始调整,并认为它有额外的一重黑色。这里额外一重黑色是什么意思呢,我们不是把红黑树的结点加上除红与黑的另一种颜色,这里只是一种假设,我们认为我们当前指向它,因此空有额外一种黑色,可以认为它的黑色是从它的父结点被删除后继承给它的,它现在可以容纳两种颜色,如果它原来是红色,那么现在是红+黑,如果原来是黑色,那么它现在的颜色是黑+黑。有了这重额外的黑色,原红黑树性质5就能保持不变。现在只要恢复其它性质就可以了,做法还是尽量向根移动和穷举所有可能性。

有如下情况:

  • 当前结点是红+黑色
    解法,直接把当前结点染成黑色,结束此时红黑树性质全部恢复。

  • 当前结点是黑+黑且是根结点, 解法:什么都不做,结束。

  • 删除修复情况1:当前结点是黑+黑且兄弟结点为红色(此时父结点和兄弟结点的子结点分别为黑) ==> 把父结点染成红色,把兄弟结点染成黑色,之后重新进入算法。

  • 删除修复情况2:当前结点是黑加黑且兄弟是黑色且兄弟结点的两个子结点全为黑色

  • 删除修复情况3:当前结点颜色是黑+黑,兄弟结点是黑色,兄弟的左子是红色,右子是黑色

  • 删除修复情况4:当前结点颜色是黑-黑色,它的兄弟结点是黑色,但是兄弟结点的右子是红色,兄弟结点左子的颜色任意

现在分别先说下

情景1: 当前节点是黑+黑,且兄弟节点为红色(此时父节点和兄弟节点的子节点为黑)

解法:把父结点染成红色,把兄弟结点染成黑色,之后重新进入算法(我们只讨论当前结点是其父结点左孩子时的情况)。此变换后原红黑树性质5不变,而把问题转化为兄弟结点为黑色的情况(注:变化前,原本就未违反性质5,只是为了把问题转化为兄弟结点为黑色的情况)。 即如下代码操作:

//调用RB-DELETE-FIXUP(T, x) 的1-8行代码
while x ≠ root[T] and color[x] = BLACK
    do if x = left[p[x]]
          then w ← right[p[x]]
               if color[w] = RED
                  then color[w] ← BLACK                        ▹  Case 1
                       color[p[x]] ← RED                       ▹  Case 1
                       LEFT-ROTATE(T, p[x])                    ▹  Case 1
                       w ← right[p[x]]                         ▹  Case 1

变化前:

在这里插入图片描述

变化后:

在这里插入图片描述

删除修复情况2:当前结点是黑加黑且兄弟是黑色且兄弟结点的两个子结点全为黑色。

解法:把当前结点和兄弟结点中抽取一重黑色追加到父结点上,把父结点当成新的当前结点,重新进入算法。(此变换后性质5不变),即调用RB-INSERT-FIXUP(T, z) 的第9-10行代码操作,如下:

//调用RB-DELETE-FIXUP(T, x) 的9-11行代码
if color[left[w]] = BLACK and color[right[w]] = BLACK
    then color[w] ← RED                          ▹  Case 2
         x p[x]                                  ▹  Case 2

变化前:

在这里插入图片描述
变化后:

在这里插入图片描述

删除修复情况3:当前结点颜色是黑+黑,兄弟结点是黑色,兄弟的左子是红色,右子是黑色。

解法:把兄弟结点染红,兄弟左子结点染黑,之后再在兄弟结点为支点解右旋,之后重新进入算法。此是把当前的情况转化为情况4,而性质5得以保持,即调用RB-INSERT-FIXUP(T, z) 的第12-16行代码,如下所示:

//调用RB-DELETE-FIXUP(T, x) 的第12-16行代码
else if color[right[w]] = BLACK
        then color[left[w]] ← BLACK          ▹  Case 3
             color[w] ← RED                  ▹  Case 3
             RIGHT-ROTATE(T, w)              ▹  Case 3
             w ← right[p[x]]                 ▹  Case 3

变化前

在这里插入图片描述

变化后

在这里插入图片描述

删除修复情况4:当前结点颜色是黑-黑色,它的兄弟结点是黑色,但是兄弟结点的右子是红色,兄弟结点左子的颜色任意。

解法:把兄弟结点染成当前结点父结点的颜色,把当前结点父结点染成黑色,兄弟结点右子染成黑色,之后以当前结点的父结点为支点进行左旋,此时算法结束,红黑树所有性质调整正确,即调用RB-INSERT-FIXUP(T, z)的第17-21行代码,如下所示:

//调用RB-DELETE-FIXUP(T, x) 的第17-21行代码
color[w] ← color[p[x]]                 ▹  Case 4
color[p[x]] ← BLACK                    ▹  Case 4
color[right[w]] ← BLACK                ▹  Case 4
LEFT-ROTATE(T, p[x])                   ▹  Case 4
x ← root[T]                            ▹  Case 4

变化前:

在这里插入图片描述

变化后:

在这里插入图片描述

挑战

  • 使用java,c,python,go,dart实现红黑树。

参考文章

初识红黑树
上一个连接的优化版本

最后

希望和你成为朋友~一起学习,一起进步
在这里插入图片描述

posted @ 2020-06-07 12:51  方家小白  阅读(12)  评论(0编辑  收藏  举报