复习:红黑树,是一种二叉查找树,是接近平衡的。

复习二叉查找树的一般性质:

1、root.left.key < root.key < root.right.key

  • 不存在键值相同的结点。

2、查找、插入、删除等操作,时间复杂度为O(lgn)。因为,一棵由n个结点,随机构造的二叉查找树的高度为lgn,所以顺理成章,一般操作的执行时间为O(lgn)。

3、二叉查找树的特殊情况:一棵具有n个结点的线性链,则此些操作最坏情况运行时间为O(n)。

而红黑树,能保证在最坏情况下,基本的动态几何操作的时间均为O(lgn)。

复习红黑树的性质:

图解

0)在红黑树中,叶子结点指空结点NULL。
1)每个结点要么是红的,要么是黑的。
2)根结点是黑的。
3)每个叶结点,即空结点(NIL)是黑的。(首尾必黑)
4)如果一个结点是红的,那么它的俩个儿子都是黑的。(不存在连续2红)
5)对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。(黑色完美平衡)
5-1)右5知,如果一个结点存在黑子结点,那么该结点肯定有两个子结点

复习红黑树的旋转知识:

对红黑树进行插入、删除结点等操作时,难免破坏它的基本性质。通过旋转来恢复(二叉堆也有恢复操作)。
在旋转操作中只有指针的改变,其他属性都保持不变。

左旋:A树下沉,B树上浮;

右旋:A树上浮,B树下沉。
在这里插入图片描述

/** 
 * 左旋操作 
 * @param root 根结点引用 
 * @param node 旋转的节点 
 * @return 根节点 
 */  
public static RBTreeNode leftRotate(RBTreeNode root, RBTreeNode node) {  
    if (node.right == RBTreeNode.nullNode)  
        return root;    // 左旋需要拥有右节点  
      
    RBTreeNode right = node.right;  
    // 旋转节点的右子树变为右节点的左子树  
    node.right = right.left;  
    if (node.right != RBTreeNode.nullNode)  
        node.right.p = node;  
    // 用右节点代替旋转节点位置  
    if (node.p != RBTreeNode.nullNode) {  
        right.p = node.p;  
        if (node.p.left == node)  
            node.p.left = right;  
        else  
            node.p.right = right;  
    } else {  
        root = right; // 没有父节点的节点为根结点  
        root.p = RBTreeNode.nullNode;  
    }  
    // 右节点的左子树变为旋转节点  
    right.left = node;  
    node.p = right;  
    return root;  
}  

/** 
 * 右旋操作 
 * @param root 根结点引用 
 * @param node 旋转节点 
 * @return 根节点 
 */  
public static RBTreeNode rightRotate(RBTreeNode root, RBTreeNode node) {  
    if (node.left == RBTreeNode.nullNode)  
        return root; // 右旋需要有左节点  
      
    RBTreeNode left = node.left;  
    // 旋转节点的左子树变为左节点的右子树  
    node.left = left.right;  
    if (node.left != RBTreeNode.nullNode)  
        node.left.p = node;  
    // 用左节点代替旋转节点  
    if (node.p != RBTreeNode.nullNode) {  
        left.p = node.p;  
        if (node.p.left == node)  
            node.p.left = left;  
        else   
            node.p.right = left;  
    } else {  
        root = left;  
        root.p = RBTreeNode.nullNode;  
    }  
    // 左节点的右子树变为旋转节点  
    left.right = node;  
    node.p = left;  
    return root;  
}  
复习红黑树插入、删除:
0、复习二叉搜索树的插入、删除操作
/* 插入操作 */
    // 二叉搜索树插入节点
    public static boolean insert(TreeNode root, TreeNode node) {
        TreeNode tree = root;
        TreeNode p = null;
        // 寻找合适的插入位子 p节点即为插入节点的父节点
        while (tree != null) {
            p = tree;
            if (tree.val > node.val) { // 小于比较节点 放入比较节点的左子树
                tree = tree.left;
            } else if (tree.val < node.val) { // 大于比较节点 放入比较节点的右子树
                tree = tree.right;
            } else {
                return false; // 二叉搜索树中已有相同元素节点 插入失败
            }
        }
        node.parent = p;
        if (p == null) { // root 为null node为根节点
            root = node;
        } else if (p.val > node.val) {
            p.left = node;
        } else {
            p.right = node;
        }
        return true;
    }

二叉搜索树删除节点分为三种情况:

  • case1-删除节点没有子节点:直接把删除节点的位置置空即可

  • case2-删除节点有一个子节点:用该子节点顶替删除节点的位置

  • case3-删除节点有两个子节点:若删除结点有两个子结点,用后继结点(大于删除结点的最小结点)替换删除结点,下文是在右子树中找(successor)。

  • 结合图,思考树的变化:删除结点被替代后,在不考虑结点的键值的情况下,对于树来说,可以认为删除的是替代结点
    在这里插入图片描述

  • 情景2:删除结点用其唯一的子结点替换,子结点替换为删除结点后,可以认为删除的是子结点,若子结点又有两个子结点,那么相当于转换为情景3,一直自顶向下转换,总是能转换为情景1。(对于红黑树来说,根据性质5.1,只存在一个子结点的结点肯定在树末了)

  • 情景3:删除结点用后继结点(肯定不存在左结点),如果后继结点有右子结点,那么相当于转换为情景2,否则转为为情景1。

综上所述,删除的结点可以看作删除替代结点,而替代结点最后总是在树末。有了这结论,我们讨论的删除红黑树的情景就少了很多,因为我们只考虑删除树末结点的情景了。

// 二叉搜索树删除节点
/**
      * 删除节点
      * @param key
      */
     public boolean delete(int key){
         Node curr=root;//保存当前节点
         Node parent=root;//保存当前节点父节点
         boolean isLeft=true;//记录是否是左几点
         //定位到将被删除的节点 
         while(key!=curr.key){
             if(key<=curr.key){
                 isLeft=true;//经过左节点
                 if(curr.left!=null){
                     parent=curr;
                     curr=curr.left;
                 }
             }else{
                 isLeft=false;//经过右节点
                 if(curr.right!=null){
                     parent=curr;
                     curr=curr.right;
                 }
             }
             if(curr==null){
                 return false;
             }
         }
         //如果被删除节点是叶子节点
         if(curr.left==null&&curr.right==null){
             if(curr==root){
                 root=null;
             }else if(isLeft){
                 parent.left=null;
             }else{
                 parent.right=null;
             }
         //如果被删除节点只有左节点
         }else if(curr.right==null){
             if(curr==root){
                 root=root.left;
             }else if(isLeft){
                 parent.left=curr.left;
             }else{
                 parent.right=curr.left;
             }
         //如果被删除节点只有右节点
         }else if(curr.left==null){
             if(curr==root){
                 root=root.right;
             }else if(isLeft){
                 parent.left=curr.right;
             }else{
                 parent.right=curr.right;
             }
         }else{
             Node successor=getSuccessor(curr);
             //将后继节点与被删除的父节点进行连接,完成整个替换过程
             if(curr==root){
                 root=successor;
             }else if(curr==parent.left){
                 parent.left=successor;
             }else{
                 parent.right=successor;
             }
             successor.left=curr.left;
         }
         return true;
         
     }
     
     public Node getSuccessor(Node delNode){
         Node curr=delNode.right;
         Node successor=curr;
         Node sucParent=null;
         while(curr!=null){
             sucParent=successor;
             successor=curr;
             curr=curr.left;
         }
         if(successor!=delNode.right){
             //将后继节点的子节点(只可能有右节点)替补到后继节点的位置上
             sucParent.left=successor.right;
             //将被删除的右孩子连接到后继节点的右节点上
             successor.right=delNode.right;
             //将被删除的左孩子连接到后继节点的右节点上(就是用后继节点替换掉被删除的节点)
         }
         return successor;
     }
1、红黑树的插入操作
1.1、把红黑树看成一个普通的二叉搜索树,对其插入红色的结点。

为什么是插入红色结点?因为插入一个红色结点只会破坏性质2或性质4,可以尽量避免对树的调整。

1.2、恢复红黑树性质

性质2:若根节点变成了红色,此时我们把根节点染成黑色即可。
性质4:插入节点的父节点是红色时,那就出现红色节点有一个为红色的子结点。

  • 情况1:插入节点z的父结点与叔节点同为红色

在这里插入图片描述

祖父结点由黑转红,可能也破坏了性质。指针跳转到祖父结点,继续恢复。

  • 情况2:插入节点z的叔节点y是黑色的,且z是一个右孩子
  • 情况3:插入节点z的叔节点y是黑色的,且z是一个左孩子

这两种情况可以放一起讨论,因为我们会把情况2转化为情况3,示意图如下:
在这里插入图片描述

  • 左上角为情况2,此时叔节点w为黑色,且插入节点z为父节点x的右孩子,此时我们对父节点x进行一次左旋,然后交换x和z的引用,即可转换为右上角的情况3.
  • 右上角为情况3,此时叔节点w为黑色,且插入节点z为父节点x的左孩子,此时我们进行如下操作即可恢复红黑树的性质:
  • 交换父节点x和祖父节点w的颜色
  • 对祖父节点w进行右旋

上面的操作既修正了对性质4的违反,也没有引起对其他红黑树性质的违反,因此我们此时可以结束对红黑树的性质修复工作。

/** 
 * 修復插入時违反的红黑树性质 
 * @param root 根节点引用 
 * @param node 修复节点 
 * @return 根节点 
 */  
public static RBTreeNode rbInsertFixup(RBTreeNode root, RBTreeNode node) {  
    // 修复节点不是根节点且为红色时  
    RBTreeNode parent = node.p, grandParent, parentBorther;  
    while(parent != RBTreeNode.nullNode && parent.color == RBColor.RED) {  
        grandParent = parent.p;  
        if (grandParent.left == parent) { // 父节点为左节点  
            parentBorther = grandParent.right; // 叔节点为右节点  
            if (parentBorther != RBTreeNode.nullNode && parentBorther.color == RBColor.RED) { // case 1  
                grandParent.color = RBColor.RED; // 祖父节点改为红色  
                parent.color = RBColor.BLACK; // 父节点和叔节点改为黑色  
                parentBorther.color = RBColor.BLACK;  
                node = grandParent; // 对祖父节点继续遍历  
            } else {  
                if (parent.right == node) { // case 2  
                    root = leftRotate(root, parent); // 对父节点左旋  
                    // 交换node和parent的引用  
                    RBTreeNode temp = node;  
                    node = parent;  
                    parent = temp;  
                }  
                // case 3  
                grandParent.color = RBColor.RED; // 祖父染成红色  
                parent.color = RBColor.BLACK; // 父节点染成黑色  
                root = rightRotate(root, grandParent); // 对祖父右旋  
                node = root; // 把节点置为根节点退出修复  
            }  
        } else { // 父节点为右节点,镜像处理  
            parentBorther = grandParent.left;  
            if (parentBorther != RBTreeNode.nullNode && parentBorther.color == RBColor.RED) { // case 1  
                grandParent.color = RBColor.RED;  
                parent.color = RBColor.BLACK;  
                parentBorther.color = RBColor.BLACK;  
                node = grandParent;  
            } else {  
                if (parent.left == node) { // case 2  
                    root = rightRotate(root, parent);  
                    RBTreeNode temp = node;  
                    node = parent;  
                    parent = temp;  
                }  
                // case 3  
                grandParent.color = RBColor.RED;  
                parent.color = RBColor.BLACK;  
                root = leftRotate(root, grandParent);  
                node = root;  
            }  
        }  
        parent = node.p;  
    }  
    // 根节点染为黑色  
    root.color = RBColor.BLACK;  
    return root;  
}  
2、红黑树的删除操作
2.1、把红黑树看成一个普通的二叉搜索树,对其删除结点

由于删除节点会带走一种颜色,因此我们需要记录下被删除的颜色和删除颜色的位置,最后我们再考虑如何修复树的红黑性质。
思考:删除操作什么情况需要修复?

2.2、恢复红黑树性质

删除操作会影响红黑树的什么性质?

  • 如果删除的节点颜色为红色,则不会影响任何红黑性质。
  • 如果删除的颜色是黑色,则可能影响性质2(根节点是黑色的),也可能影响性质4(红色的节点的两个子结点均为黑色),也可能影响性质5(对于每个节点,从该节点到其所有后代的简单路径上,均包含相同数目的黑色节点)。

思考2种删除情况的恢复方式:

  • 删除情景1:替换结点是红色结点
    处理:颜色变为删除结点的颜色

  • 删除情景2:替换结点是黑结点
    处理:分4种情况讨论,此处假设修复位置节点为A(黑色,此处假设为父节点的左节点,右节点请镜像处理),其父节点为B,兄弟节点为C,兄弟节点的左子节点为D,兄弟节点的右子节点为E。

  • 情况1:A的兄弟节点为红色

在这里插入图片描述
如上图所示,此时我们先交换父节点B和兄弟节点C的颜色,然后对父节点B进行左旋,以上操作并不会影响红黑树性质,而我们也把情况1转化为了别的情况。

  • 情况2:A的兄弟节点为黑色,其子节点均为黑色(下图灰色代表未知颜色)

在这里插入图片描述
此时的处理方法很简单,因为A节点和其兄弟节点C均为黑色,且C的子节点也均为黑色,因此我们可以把A节点和C节点的黑色上移到父节点B上,再把修复位置换为父节点B,针对父节点B继续进行修复。(如果父节点B是红色或根节点就可以停止修复了~)

  • 情况3:A的兄弟节点为黑色,兄弟节点的左子节点为红色,右子节点为黑色

在这里插入图片描述
此时我们首先交换兄弟节点C与其左子红色节点D的颜色,然后对兄弟节点C进行右旋,把情况3转化为情况4继续处理。

  • 情况4:A的兄弟节点为黑色,兄弟节点的右子节点为红色

在这里插入图片描述
此时我们进行如下变换操作:
1、把父节点B和兄弟节点的右子节点E染成黑色,兄弟节点C染成父节点颜色
2、对父节点B进行左旋

删除修复过程的Java代码如下:

/** 
 * 红黑树删除操作 
 * @param root 根节点引用 
 * @param deleteNode 要删除的节点 
 * @return 根节点 
 */  
public static RBTreeNode rbDelete(RBTreeNode root, RBTreeNode deleteNode) {  
    RBTreeNode replaceNode, fixNode = RBTreeNode.nullNode; // 顶替删除节点的代替节点、需要修复颜色的节点位置  
    RBTreeNode fixNodeParent = deleteNode.p;  
    RBColor deleteColor = deleteNode.color; // 记录被删除节点的颜色  
    if (deleteNode.left == RBTreeNode.nullNode && deleteNode.right == RBTreeNode.nullNode) // 删除节点没有任何子结点  
        replaceNode = RBTreeNode.nullNode;  
    else if (deleteNode.right == RBTreeNode.nullNode) { // 处理只有左子节点的情况  
        replaceNode = deleteNode.left;  
        fixNode = replaceNode;  
    } else if (deleteNode.left == RBTreeNode.nullNode) { //处理只有右子节点的情况  
        replaceNode = deleteNode.right;  
        fixNode = replaceNode;  
    } else { // 处理有两个子节点的情况,寻找sucessor
        replaceNode = deleteNode.right;  
        while (replaceNode.left != RBTreeNode.nullNode) // 找到右子树的最小节点  
            replaceNode = replaceNode.left;  
        fixNode = replaceNode.right; // 修复节点位置变为原顶替节点位置
          
        if (replaceNode.p == deleteNode) { // 特殊情况,前面的while没有执行,表明右子树没有左节点。
            if (fixNode != RBTreeNode.nullNode) // 修复节点不为空  
                fixNode.p = replaceNode;  
            fixNodeParent = replaceNode;  
        } else {  
            replaceNode.p.left = fixNode; // 修复节点顶替该节点的位置  
            if (fixNode != RBTreeNode.nullNode) // 修复节点不为空  
                fixNode.p = replaceNode.p;  
            fixNodeParent = replaceNode.p;  
            replaceNode.right = deleteNode.right;  
        }  
        // 用删除节点的颜色覆盖顶替节点的颜色  
        deleteColor = replaceNode.color;  
  
        replaceNode.color = deleteNode.color;   
        replaceNode.left = deleteNode.left;  
    }  
    if (replaceNode != RBTreeNode.nullNode) // 存在顶替节点  
        replaceNode.p = deleteNode.p;  
    if (deleteNode.p == RBTreeNode.nullNode) // 删除节点的父节点为空,是根节点  
        root = replaceNode;  
    else { // 删除节点不是根节点  
        if (deleteNode.p.left == deleteNode)  
            deleteNode.p.left = replaceNode;  
        else  
            deleteNode.p.right = replaceNode;  
    }  
    if (deleteColor == RBColor.BLACK) // 如果删除的颜色是黑色则需要进行修复  
        root = rbDeleteFixup(root, fixNode, fixNodeParent);  
    return root;  
}  

/** 
 * 修复删除时破坏的红黑树性质 
 * @param root 根引用 
 * @param fixNode 修复位置 
 * @param parent 修复位置的父节点(修复位置为叶结点时使用) 
 * @return 根 
 */  
public static RBTreeNode rbDeleteFixup(RBTreeNode root, RBTreeNode fixNode, RBTreeNode parent) {  
    RBTreeNode brother;  
    while (root != fixNode && fixNode.color == RBColor.BLACK) {  
        parent = fixNode.p == null ? parent : fixNode.p; // 处理fixNode为nullNode情况  
        if (fixNode == parent.left) { // 顶替位置在父节点左边  
            brother = parent.right;  
            if (brother.color == RBColor.RED) { // case 1  
                // 交换父节点和兄弟节点的颜色  
                RBColor temp = brother.color;  
                brother.color = parent.color;  
                parent.color = temp;  
                // 父节点进行左旋  
                root = leftRotate(root, parent);  
            } else if (brother == RBTreeNode.nullNode) { // case 2  
                // 兄弟节点为空,即为黑色,只需继续遍历父节点即可  
                fixNode = parent;  
            } else if (brother.left.color == RBColor.BLACK &&  
                    brother.right.color == RBColor.BLACK) { // case 2  
                brother.color = RBColor.RED;  
                fixNode = parent; // 继续遍历父节点  
            } else { // case 3 and case 4   
                if (brother.left.color == RBColor.RED &&  
                    brother.right.color == RBColor.BLACK) { // case 3  
                    // 兄弟节点染成红色,左子节点染成黑色  
                    brother.color = RBColor.RED;  
                    brother.left.color = RBColor.BLACK;  
                    // 兄弟节点右旋  
                    root = rightRotate(root, brother);  
                    brother = brother.p;  
                }  
                // case 4  
                // 变色  
                brother.color = parent.color;  
                parent.color = RBColor.BLACK;  
                brother.right.color = RBColor.BLACK;  
                // 父节点左旋  
                root = leftRotate(root, parent);  
                break;  
            }  
        } else {  
            brother = parent.left;  
            if (brother.color == RBColor.RED) { // case 1  
                // 交换父节点和兄弟节点的颜色  
                RBColor temp = brother.color;  
                brother.color = parent.color;  
                parent.color = temp;  
                // 父节点进行右旋  
                root = rightRotate(root, parent);  
            } else if (brother == RBTreeNode.nullNode) { // case 2  
                // 兄弟节点为空,即为黑色,只需继续遍历父节点即可  
                fixNode = parent;  
            } else if (brother.left.color == RBColor.BLACK &&  
                    brother.right.color == RBColor.BLACK) { // case 2  
                brother.color = RBColor.RED;  
                fixNode = parent; // 继续遍历父节点  
            } else { // case 3 and case 4   
                if (brother.right.color == RBColor.RED &&  
                    brother.left.color == RBColor.BLACK) { // case 3  
                    // 兄弟节点染成红色,左子节点染成黑色  
                    brother.color = RBColor.RED;  
                    brother.right.color = RBColor.BLACK;  
                    // 兄弟节点右旋  
                    root = leftRotate(root, brother);  
                    brother = brother.p;  
                }  
                // case 4  
                // 变色  
                brother.color = parent.color;  
                parent.color = RBColor.BLACK;  
                brother.left.color = RBColor.BLACK;  
                // 父节点左旋  
                root = rightRotate(root, parent);  
                break;  
            }  
        }  
    }  
    fixNode.color = RBColor.BLACK;  
    return root;  
}; 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值