红黑树的插入与删除

本文内容搬运了一篇博客以及java TreeMap实现的源码,还有一些自己的体会。本以为红黑树的插入操作就挺难实现了,结果看到了红黑树的删除操作,发现自己还是太年轻了,首先看一下红黑树的性质:

  1. 每个结点要么是红的要么是黑的。  
  2. 根结点是黑的。  
  3. 每个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。  
  4. 如果一个结点是红的,那么它的两个儿子都是黑的。  
  5.  对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点。 
根据性质我们可以发现,当插入新的结点时(每次插入的结点颜色都置为红),性质1、3、5是肯定不会发生改变的,可能发生改变的只有性质2、4,因此插入后我们需要适当的旋转改色来维持红黑树的再次平衡,可以分为以下几种情况:(插入规则与BST相同)

1、若插入结点的父亲结点p与p的兄弟结点都为红色,我们可以将p与p的兄弟结点都置为黑色,将p的父亲结点置为红色,这样维护了性质4。

2、若插入结点的父亲结点p为红色(p的兄弟结点不为红),且插入结点为p的右孩子,那我们将p左旋(关于左旋和右旋的介绍很多,也易于理解,这里就不详细解释了),变成情况3.

3、若插入结点的父亲结点p为红色(p的兄弟结点不为红),且插入结点为p的左孩子,将p置黑,将p的父亲结点置红(p的父亲结点必为黑否则不成立),将p的父亲结点右旋。

以上规则可以自己画图实验一下,非常直观,代码如下:

private void fixAfterInsertion(Entry<K,V> x) 
 { 
    x.color = RED; 
    // 直到 x 节点的父节点不是根,且 x 的父节点不是红色
    while (x != null && x != root 
        && x.parent.color == RED) 
    { 
        // 如果 x 的父节点是其父节点的左子节点
        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) 
        { 
            // 获取 x 的父节点的兄弟节点
            Entry<K,V> y = rightOf(parentOf(parentOf(x))); 
            // 如果 x 的父节点的兄弟节点是红色
            if (colorOf(y) == RED) 
            { 
                // 将 x 的父节点设为黑色
                setColor(parentOf(x), BLACK); 
                // 将 x 的父节点的兄弟节点设为黑色
                setColor(y, BLACK); 
                // 将 x 的父节点的父节点设为红色
                setColor(parentOf(parentOf(x)), RED); 
                x = parentOf(parentOf(x)); 
            } 
            // 如果 x 的父节点的兄弟节点是黑色
            else 
            { 
                // 如果 x 是其父节点的右子节点
                if (x == rightOf(parentOf(x))) 
                { 
                    // 将 x 的父节点设为 x 
                    x = parentOf(x); 
                    rotateLeft(x); 
                } 
                // 把 x 的父节点设为黑色
                setColor(parentOf(x), BLACK); 
                // 把 x 的父节点的父节点设为红色
                setColor(parentOf(parentOf(x)), RED); 
                rotateRight(parentOf(parentOf(x))); 
            } 
        } 
        // 如果 x 的父节点是其父节点的右子节点
        else 
        { 
            // 获取 x 的父节点的兄弟节点
            Entry<K,V> y = leftOf(parentOf(parentOf(x))); 
            // 如果 x 的父节点的兄弟节点是红色
            if (colorOf(y) == RED) 
            { 
                // 将 x 的父节点设为黑色。
                setColor(parentOf(x), BLACK); 
                // 将 x 的父节点的兄弟节点设为黑色
                setColor(y, BLACK); 
                // 将 x 的父节点的父节点设为红色
                setColor(parentOf(parentOf(x)), RED); 
                // 将 x 设为 x 的父节点的节点
                x = parentOf(parentOf(x)); 
            } 
            // 如果 x 的父节点的兄弟节点是黑色
            else 
            { 
                // 如果 x 是其父节点的左子节点
                if (x == leftOf(parentOf(x))) 
                { 
                    // 将 x 的父节点设为 x 
                    x = parentOf(x); 
                    rotateRight(x); 
                } 
                // 把 x 的父节点设为黑色
                setColor(parentOf(x), BLACK); 
                // 把 x 的父节点的父节点设为红色
                setColor(parentOf(parentOf(x)), RED); 
                rotateLeft(parentOf(parentOf(x))); 
            } 
        } 
    } 
    // 将根节点设为黑色
    root.color = BLACK; 
 } 
红黑树的删除操作相对要复杂许多,首先删除结点的过程与BST是一样的,分别考虑删除结点有几个孩子,若是2个需要找到后继结点代替该结点,若是一个或没有就比较简单了。执行完删除后最重要的是维持树的完美平衡,若是删除红结点不存在这种情况,若删除的是黑结点肯定会违背性质5,因此又要进行旋转和调色,这个过程非常复杂,我这里搬运一下我认为讲的比较清楚的一篇博客:http://blog.csdn.net/spch2008/article/details/9338923

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

 

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

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


因为上述的情况,红黑树处于不平衡状态。(破坏到null,黑色节点数目相同)

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


第二:删除单支黑节点

         

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


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

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


删除规则:用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子树中找一合适红色节点,将其置黑,使得x子树与w子树达到平衡。

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

    

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

      

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

      

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


      情况2:x的兄弟w为黑色,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可能达到平衡,情况4一定会达到平衡,以上这四种情况是x为其父亲结点的左子结点,我觉得如果能理解的话右子结点肯定也没问题了,代码如下:

// 删除节点后修复红黑树
 private void fixAfterDeletion(Entry<K,V> x) 
 { 
    // 直到 x 不是根节点,且 x 的颜色是黑色
    while (x != root && colorOf(x) == BLACK) 
    { 
        // 如果 x 是其父节点的左子节点
        if (x == leftOf(parentOf(x))) 
        { 
            // 获取 x 节点的兄弟节点
            Entry<K,V> sib = rightOf(parentOf(x)); 
            // 如果 sib 节点是红色
            if (colorOf(sib) == RED) 
            { 
                // 将 sib 节点设为黑色
                setColor(sib, BLACK); 
                // 将 x 的父节点设为红色
                setColor(parentOf(x), RED); 
                rotateLeft(parentOf(x)); 
                // 再次将 sib 设为 x 的父节点的右子节点
                sib = rightOf(parentOf(x)); 
            } 
            // 如果 sib 的两个子节点都是黑色
            if (colorOf(leftOf(sib)) == BLACK 
                && colorOf(rightOf(sib)) == BLACK) 
            { 
                // 将 sib 设为红色
                setColor(sib, RED); 
                // 让 x 等于 x 的父节点
                x = parentOf(x); 
            } 
            else 
            { 
                // 如果 sib 的只有右子节点是黑色
                if (colorOf(rightOf(sib)) == BLACK) 
                { 
                    // 将 sib 的左子节点也设为黑色
                    setColor(leftOf(sib), BLACK); 
                    // 将 sib 设为红色
                    setColor(sib, RED); 
                    rotateRight(sib); 
                    sib = rightOf(parentOf(x)); 
                } 
                // 设置 sib 的颜色与 x 的父节点的颜色相同
                setColor(sib, colorOf(parentOf(x))); 
                // 将 x 的父节点设为黑色
                setColor(parentOf(x), BLACK); 
                // 将 sib 的右子节点设为黑色
                setColor(rightOf(sib), BLACK); 
                rotateLeft(parentOf(x)); 
                x = root; 
            } 
        } 
        // 如果 x 是其父节点的右子节点
        else 
        { 
            // 获取 x 节点的兄弟节点
            Entry<K,V> sib = leftOf(parentOf(x)); 
            // 如果 sib 的颜色是红色
            if (colorOf(sib) == RED) 
            { 
                // 将 sib 的颜色设为黑色
                setColor(sib, BLACK); 
                // 将 sib 的父节点设为红色
                setColor(parentOf(x), RED); 
                rotateRight(parentOf(x)); 
                sib = leftOf(parentOf(x)); 
            } 
            // 如果 sib 的两个子节点都是黑色
            if (colorOf(rightOf(sib)) == BLACK 
                && colorOf(leftOf(sib)) == BLACK) 
            { 
                // 将 sib 设为红色
                setColor(sib, RED); 
                // 让 x 等于 x 的父节点
                x = parentOf(x); 
            } 
            else 
            { 
                // 如果 sib 只有左子节点是黑色
                if (colorOf(leftOf(sib)) == BLACK) 
                { 
                    // 将 sib 的右子节点也设为黑色
                    setColor(rightOf(sib), BLACK); 
                    // 将 sib 设为红色
                    setColor(sib, RED); 
                    rotateLeft(sib); 
                    sib = leftOf(parentOf(x)); 
                } 
                // 将 sib 的颜色设为与 x 的父节点颜色相同
                setColor(sib, colorOf(parentOf(x))); 
                // 将 x 的父节点设为黑色
                setColor(parentOf(x), BLACK); 
                // 将 sib 的左子节点设为黑色
                setColor(leftOf(sib), BLACK); 
                rotateRight(parentOf(x)); 
                x = root; 
            } 
        } 
    } 
    setColor(x, BLACK); 
 } 
想要一次理解红黑树的删除操作是很困难的,本人目前也是属于一知半解,只能循序渐进吧。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值