红黑树

红黑树

红黑树是一种特殊的二叉查找树(binary search tree,以下简称BST),它用来解决BST的致命缺点。BST在动态查找时,不能保证树的平衡性,尤其关键字有序地插入时,二叉树的查找效率等价于链表了。红黑树的目的在于时刻保持二叉树的平衡性,即保证二叉树的高度最小来保证二叉树的查找效率。

五个基本特性

  1. 节点颜色非黑即白

  2. 根节点必须是黑色的

  3. 叶子节点必须是黑的

  4. 红色节点的孩子为黑色

  5. 每个节点到所有叶子节点的路径上经过的黑色节点数(黑高度)相同

二叉树基本操作

  1. 旋转

    rotate.jpg

    左旋

    Left-Rotate(T, y)
        //1.处理y的左孩子β
        x.r = y.l
        if(y.l != T.nil)
            y.l.p = x
    
        //2.处理父节点
        y.p = x.p
        if(x.p == T.nil)
            T.root = y
        else if(x.p.l == x)
            x.p.l = y
        else
            x.p.r = y
    
        //3.处理x,y的关系
        y.l = x
        x.p = y
    

    右旋

    Right-Rotate(T, x)
        //1.处理x的右孩子β
        y.l=x.r
        if(x.r != T.nil)
            x.r.p = y
    
        //2.处理父节点
        x.p = y.p
        if(y.p == T.nil)
            T.root = x
        else if(y.p.l == y)
            y.p.l = x
        else 
            y.p.r = x
    
        //3.处理x,y的关系
        x.r = y
        y.p = x
    
  2. 取代

    即用子树v取代子树u。需要注意最后一行,u.p为T.nil时 v.p = u.p也执行,这是v为root,v.p为T.nil

    RB-Transplant(T, u, v)
        if(u.p == T.nil)
            T.root = v
        else if(u.p.l == u)
            u.p.l = v
        else
            u.p.r = v
        v.p = u.p
    
  3. 求 min/max

    Tree-Min(x)
        while(x != NIL)
            x = x.l
        return x
    
    Tree-Max(x)
        while(x != NIL)
            x = x.r
        return x
    
  4. 查找

    循环方式

    Tree-Search(x, k)
        while(x != NIL and x.key != k)
            if(x.key < k)
                x = x.r
            else
                x = x.l
        return x
    

    递归方式

    Tree-Search(x, k)
        if(x == NIL or x.key == k)
            if(x.key < k)
                Tree-Search(x.r, k)
            else
                Tree-Search(x.l, k)
    
  5. 插入

    RB-Insert(T, z)
        y = T.nil
        x = T.root
    
        while(x != T.nil)
            if(x.key< z.key)
                x = x.r
            else
                x= x.l
    
        z.p = x
    
        if(x == T.nil)
            T.root = z
        else if(x.key < z.key)
            x.r = z
        else 
            x.l = z
    
        z.l = T.nil
        z.r = T.nil
        z.color = RED
    
        RB-Insert-Fix(T, z)
    

    当这个红色节点插入后,会违反哪些红黑树的规则?

    • 规则2:根节点为黑色。原本树为空,当插入红节点后,违反此规则

    • 规则4:红色节点的孩子为黑色。新节点z的父节点y原本为红色,插入后红色节点有了红孩子,出现了两个相邻的红色节点。

    case1: 新节点z的叔叔节点y为红色。 不论新节点z为其父节点的左右孩子,操作是一样的。

    rb-insert-case1.jpg
    y = z.p.p.r
        if(uncle.color == RED)
            z.p.color = BLACK
            y.color = BLACK
            z.p.p.color = RED
            z = z.p.p
    

    case2,3: 新节点z的叔叔节点y为黑色。

    case2和case3按z是其父节点的左孩子还是右孩子分开,其实二者只差一个旋转

    rb-insert-case2-3.jpg
    y = z.p.p.r
    if (z == z.p.r)
        z = z.p
        Left_Rotate(T, z)
    z.p.p.color = RED
    z.p.color = BLACK
    Right_Rotate(T, z.p.p)
    
    

    最终程序

    RB-Insert-Fix(T, z)
        while(z.p.color == RED)
            if(z.p == z.p.p.l)
                y = z.p.p.r
                if (uncle.color == RED)
                    z.p.color = BLACK
                    y.color = BLACK
                    z.p.p.color = RED
                    z = z.p.p
                else if (z == z.p.r)
                        z = z.p
                        Left_Rotate(T, z)
                    z.p.p.color = RED
                    z.p.color = BLACK
                    Right_Rotate(T, z.p.p)
            else //symmetic
                ...
    
        T.root.color = BLACK
    

    总结:

    • 程序按z.p为左孩子还是右孩子分为镜像的两部分。
    • while循环用来处理违反红黑树规则4的情况,因为处理过程中新节点z会不断更新上移,所以使用了while循。
    • 最后一行用来处理违反红黑树规则2的情况。
  6. 删除

    case1:

    z的左右孩子至少有一个为空,直接用非空的孩子替代z。

    bst-delete-case1.jpg
    if (z.l == T.nil)
        x = z.r
        RB-Transplant(T, z, x)
    else if (z.r == T.nil)
        x = z.l
        RB-Transplant(T, z, x)
    

    case2:

    z的左右孩子都不为空,且z的后继是其右孩子。直接用其右孩子替代z。

    bst-delete-case2.jpg
    if (z.l !== T.nil and z.l=r !== T.nil)
        y = Tree-Min(T, z.r)
        y-original-color = y.color
        if(y.p == z)
            x.p = y
        //y取代z
        RB-Transplant(T, z, y)
        //安排z.l
        y.l = z.l
        z.l.p = y
        //继承颜色
        y.clolr = z.color
    

    case3:

    bst-delete-case3.jpg

    按照中序遍历依次为 z, y, x, r, 如果删除z,后继们要依次补上。这里操作为
    x覆盖y, r覆盖x,然后y覆盖z。

    if (y.p != z)
        //1. x覆盖y
        BR-Transplant(T, y, y.r)
        //2. z的右孩子成为y的右孩子,这时x仍然为y的后继
        y.r = z.r
        z.r.p = y
        //3. y覆盖z
        RB-Transplant(T, z, y)
    
        y.l = z.l
        y.l.p = y
        y.color = z.color
    

    最终程序

    RB-Delete(T, z)
        //case 1
        y = z
        y-original-color = y.color
        if (z.l == T.nil)
            x = z.r
            RB-Transplant(T, z, x)
        else if (z.r == T.nil)
            x = z.l
            RB-Transplant(T, z, x)
        else
            y = Tree-Min(T, z.l)
            y-original-color = y.color
            x = y.r
            //case 2
            if (y.p == z)
                x.p = y
            //case 3
            else 
                RB-Transplant(T, y, x)
                y.r = z.r
                z.r.p = y
            BR-Transplant(T, z, y)
            //处理l
            y.l = z.l
            z.l.p = y
            y.color = z.color
    
        if (y-original-color == BLACK)
            BR-Delete-Fix(T, x)
    

    几个解释点:

    • x变量,指向y的右孩子,是y的后继。程序中需要它覆盖y。

    • y-original-color
      记录y的原始颜色,因为在调用tansplant函数后y的颜色可能会改变。y的原色时决定是否要执行修正的关键:红不违反,黑违反。

    为什么黑违反:

    • 规则2:y为根(一开始赋值y=z),孩子x为红,x取代y后出现根为红色的情况。
    • 规则4:x取代y后,x和x.p都为红色
    • 规则5:y被取代后,之前包含y的路径中黑节点的数量少1。

    为什么红不违反:

    • y为红,不可能为根节点
    • y为红,x必为黑,x取代y后,x与x.p不可能同时为红
    • y为红,被取代后,黑高度不变

修正

其实违反规则5的情况可以转化为违反了规则1(节点颜色非黑即白):为弥补包含y的路径上黑高度少一的情况,我们给x加一个黑色属性,(即x为balck-red或black-black),同时在修正操作中保证不违反规则5这样就是实现完美转化了。所以,我们要修正的对象是规则1,2,4

修复规则2,当红节点作为根节点时,只需要直接把x的颜色(red-black)设置为黑色即可

修复规则4,违反这一规则的情况只有一种,那就是x取代y后,x与x.p同时为红,因为这时x的兄弟必为黑。这时也只需要把x的颜色(red-black)直接设置为黑即可。

if (x.color == RED or T.root == x)
    x.color = BLACK

剩下的就是修复规则1了。这是相对复杂的流程。流程的核心思想就是把x多出来的黑色不断向上转移。

分情况讨论

图例说明,黑色表示黑,深灰色表示红,浅灰色表示该节点任意色可黑可红,用c表示。w表示x的兄弟节点。

case1 x的兄弟节点w为红色

rb-delete-fix-case1.jpg

B D互换颜色,然后以B为轴点左旋, w更新为老w的一个孩子之一,这时w必为黑色,由此,case1装换后进入了case2,3,4。目的就是让x与w同为黑色,这样去除x额外的黑色数时方便一点。

if (w.color == RED)
    x.p.color = RED
    w.color = BLACK
    Left-Rotate(T, x.p)
    //w 继续成为x的兄弟,他来自老w的一个孩子,黑色
    w = x.p.r

case2 w为黑,x的两个孩子为黑

rb-delete-fix-case2.jpg

同时从x与w上拿掉一点黑色数:w变红,x的黑色数向上转移,把新的x设置为x的父节点。

if(w.color == BLACK and w.left.color == BLACK and w.right.color == BLACK)
    w.color = RED
    x = x.p

case3 w为黑,x的孩子左红右黑

rb-delete-fix-case3.jpg

C D 交换颜色,以w为轴点右旋,w设置为x的兄弟。

完全没啥道理,如果程序执行过程中遇到这种情况就把他向case4转化好了,是一个过渡case。但要保证转化的过程中黑高度不变。

w.l.color = BLACK
w.color = RED
Right_Rotate(T, w)
w = x.p.r

case4 w为黑,x的右孩子为红,左孩子任意

rb-delete-fix-case4.jpg

D变为B的颜色,B,E变为黑色,以B为轴点左旋。

这很玄学, 需要注意程序最后,让变量x指向根节点也没啥意思,目的就是为了终止循环,因为这时x额外的黑色树已经移除了,规则1修复完毕。

w.color = x.p.cololr
x.p.color = BLACK
w.right.color = BLACK
Left-Rotate(T, x.p)
x = T.root

至此规则1修复完毕。核心就是使x额外的黑色数向上移,但保证变换前后黑高度不变。接下来就是代码整合了。应为这看似是4个case,其实他们之间是有前后关系的,可不是简单的if/else就能搞定。即使搞定了程序也不简洁。

代码汇总

RB-Delete-Fix(T, x)
    while(x != T.root and x.color == BLACK)
        if(x == x.p.l)
            w = x.p.r
            //-------------------------case1-----------------------
            if(w.color == RED)
                w.color = BLACK
                x.p.color = RED
                Left-Rotate(T, x.p)
                //w 继续成为x的兄弟,他来自老w的一个孩子,黑色,这时w变黑了
                w = x.p.r
            //-------------------------case2------------------------
            //这里没有用else,因为case1如果执行,w变黑,会自动转到case2,3,4
            if (w.l.color == BLACK and w.r.color == BLACK)
                w.color = RED
                x = x.p
            //-------------------------case3------------------------
            else if (w.r.color == BLACK)
                    w.l.color = BLACK
                    w.color = RED
                    Right-Rotate(T, w)
                    w = x.p.r
            //-------------------------case4-----------------------
                w.color = x.p.color
                x.p.color = BLACK
                w.r.color = BLACK
                Lefte-Rotate(T, x.p)
                x = T.root

        else //symmetric

    x.color = BLACK

应用
java的TreeMap中使用了红黑树,可以去看看相关的源码

参考文献

[1]Cormen, T., &Leiserson, C., &Rivest, R., &Stein, C.(2009). Introduction to Algorithms(pp.287-329).London:The MIT Press.

你可能感兴趣的:(红黑树)