zoukankan      html  css  js  c++  java
  • 数据结构之三-红黑树

    概述

      搜索二叉树在插入的数据是有序的时候会非常不平衡,几乎变成了线性结构,如插入数据顺序为10,20,30,40,50,那么该二叉树的结构会如下图所示,那么这样就和链表没啥区别,查找的时间复杂度就为O(n),而不是O(logN),为了以较快的时间搜索一颗树,我们就要保证这颗树的平衡性,也就是树的左右子树节点个数趋近相等,红黑树就是这样一颗平衡树,红黑树对插入的数据项会进行检查,如果插入项破坏了树的平衡,树会自动修复。

    1.1红黑树特征

    1 树中节点非黑及红

    2 插入和删除节点必须遵循红黑规则:

        1.每个节点不是红色就是黑色的;

      2.根节点总是黑色的;

      3.如果节点是红色的,则它的子节点必须是黑色的(反之不一定),(也就是从每个叶子到根的所有路径上不能有两个连续的红色节点);

      4.从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)。

    注意:新插入的节点颜色总是红色的,这是因为插入一个红色节点比插入一个黑色节点违背红-黑规则的可能性更小,原因是插入黑色节点总会改变黑色高度(违背规则4),但是插入红色节点只有一半的机会会违背规则3(因为父节点是黑色的没事,父节点是红色的就违背规则3)另外违背规则3比违背规则4要更容易修正。当插入一个新的节点时,可能会破坏这种平衡性,那么红-黑树是如何修正的呢?

    1.2 红黑树自我修正方法

    红黑树节点实体类

    public class RBNode<T extends Comparable<T>> {
        boolean color;//颜色
        T key;//关键值
        RBNode<T> left;//左子节点
        RBNode<T> right;//右子节点
        RBNode<T> parent;//父节点
         
        public RBNode(boolean color,T key,RBNode<T> parent,RBNode<T> left,RBNode<T> right){
            this.color = color;
            this.key = key;
            this.parent = parent;
            this.left = left;
            this.right = right;
        }
         
        //获得节点的关键值
        public T getKey(){
            return key;
        }
    }
    

      

    1.2.1改变节点颜色

      新插入的节点为15,一般新插入颜色都为红色,那么我们发现直接插入会违反规则3,改为黑色却发现违反规则4。这时候我们将其父节点颜色改为黑色,父节点的兄弟节点颜色也改为黑色。通常其祖父节点50颜色会由黑色变为红色,但是由于50是根节点,所以我们这里不能改变根节点颜色。

    1.2.2 右旋

      首先要说明的是节点本身是不会旋转的,旋转改变的是节点之间的关系,选择一个节点作为旋转的顶端,如果做一次右旋,这个顶端节点会向下和向右移动到它右子节点的位置,它的左子节点会上移到它原来的位置。右旋的顶端节点必须要有左子节点。

    1.2.3 左旋

    左旋代码实现(右旋正好相反,原理一样):

    /**
         * 以C为轴做左旋
         * 左旋操作做了三件事
         * 1 将的B的左子节点赋给C的右子节点,并将C赋予B的左子节点
         * 2 将C的父节点A赋给B的父节点,同时更新A的子节点为B(区分左右)
         * 3 将B的左子节点设置为C,将C父节点设置为B
         * @param C
         */
        private void leftRotate(RBNode<T> C){
            RBNode<T> root;
            RBNode<T> B = C.right;
            /*  1 将的B的左子节点赋给C的右子节点,
                并将B的左子节点父节点指向C(B的左子节点不为空)
             */
            C.right = B.left;
            if(B.left != null){
                B.left.parent=C;
            }
            /*
                2 将C的父节点A赋给B的父节点,同时更新A的子节点为B(区分左右)
             */
            B.parent = C.parent;
            if(C.parent == null){
                root=B;//如果C为根节点则将B设置为根
            }else{
                if(C == C.parent.left){
                    C.parent.left = B;
                }else{
                    C.parent.right = B;
                }
            }
            //3 将B的左子节点设置为C,将C父节点设置为B
            B.left = C;
            C.parent = B;
        }
    

     2 插入操作

    和搜索二叉树一样,都是都是得先找到插入的位置,然后再将节点插入,最后利用旋转操作修正红黑树;修正的过程要分情况讨论。

    1:如果是第一次插入,由于原树为空,所以只会违反红-黑树的规则2,所以只要把根节点涂黑即可;

    2:如果插入节点的父节点是黑色的,那不会违背红-黑树的规则,什么也不需要做;

    但是遇到如下三种情况,我们就要开始变色和旋转了:

      ①、插入节点的父节点和其叔叔节点(祖父节点的另一个子节点)均为红色。

      ②、插入节点的父节点是红色的,叔叔节点是黑色的,且插入节点是其父节点的右子节点。

      ③、插入节点的父节点是红色的,叔叔节点是黑色的,且插入节点是其父节点的左子节点。

      下面我们挨个分析这三种情况都需要如何操作,然后给出实现代码。

      在下面的讨论中,使用N,P,G,U表示关联的节点。N(now)表示当前节点,P(parent)表示N的父节点,  

            U(uncle)表示N的叔叔节点,G(grandfather)表示N的祖父节点,也就是P和U的父节点。

            ①、插入节点的父节点和其叔叔节点均为红色。此时,肯定存在祖父节点,但是不知道父节点是其左子节点还是右子节点,但是由于对称性,我们只要讨论出一边的情况,另一种情况自然也与之对应。这里考虑父节点是其祖父节点的左子节点的情况,如下左图所示:

                    情况1

      对于这种情况,我们要做的操作有:将当前节点(4) 的父节点(5) 和叔叔节点(8) 涂黑,将祖父节点(7)涂红,变成了上有图所示的情况。再将当前节点指向其祖父节点,再次从新的当前节点开始算法(具体看下面的步骤)。这样上右图就变成情况2了。

                    情况2

    对于情况2:插入节点的父节点是红色的,叔叔节点是黑色的,且插入节点是其父节点的右子节点。我们要做的操作有:将当前节点(7)的父节点(2)作为新的节点,以新的当前节点为支点做左旋操作。完成后如左下图所示,这样左下图就变成情况3了。

                     情况3

     

      对于情况3:插入节点的父节点是红色,叔叔节点是黑色,且插入节点是其父节点的左子节点。我们要做的操作有:将当前节点的父节点(7)涂黑,将祖父节点(11)涂红,在祖父节点为支点做右旋操作。最后把根节点涂黑,整个红-黑树重新恢复了平衡,如右上图所示。至此,插入操作完成!

                    修复完的红黑树

     

    红黑树修复代码:

    private void insertFixUp(RBNode<T> node){
        RBNode<T> parent,gparent;//定义父节点和祖父节点
         
        //需要修正的条件:父节点存在,且父节点的颜色是红色
        while(((parent = parentOf(node)) != null) && isRed(parent)){
            gparent = parentOf(parent);//获得祖父节点
             
            //若父节点是祖父节点的左子节点,下面的else相反
            if(parent == gparent.left){
                RBNode<T> uncle = gparent.right;//获得叔叔节点
                 
                //case1:叔叔节点也是红色
                if(uncle != null && isRed(uncle)){
                    setBlack(parent);//把父节点和叔叔节点涂黑
                    setBlack(uncle);
                    setRed(gparent);//把祖父节点涂红
                    node = gparent;//把位置放到祖父节点处
                    continue;//继续while循环,重新判断
                }
                 
                //case2:叔叔节点是黑色,且当前节点是右子节点
                if(node == parent.right){
                    leftRotate(parent);//从父节点出左旋
                    RBNode<T> tmp = parent;//然后将父节点和自己调换一下,为下面右旋做准备
                    parent = node;
                    node = tmp;
                }
                 
                //case3:叔叔节点是黑色,且当前节点是左子节点
                setBlack(parent);
                setRed(gparent);
                rightRotate(gparent);
            }else{//若父节点是祖父节点的右子节点,与上面的情况完全相反,本质是一样的
                RBNode<T> uncle = gparent.left;
                 
                //case1:叔叔节点也是红色的
                if(uncle != null && isRed(uncle)){
                    setBlack(parent);
                    setBlack(uncle);
                    setRed(gparent);
                    node = gparent;
                    continue;
                }
                 
                //case2:叔叔节点是黑色的,且当前节点是左子节点
                if(node == parent.left){
                    rightRotate(parent);
                    RBNode<T> tmp = parent;
                    parent = node;
                    node = tmp;
                }
                 
                //case3:叔叔节点是黑色的,且当前节点是右子节点
                setBlack(parent);
                setRed(gparent);
                leftRotate(gparent);
            }
        }
        setBlack(root);//将根节点设置为黑色
    }
    

      

      

  • 相关阅读:
    LeetCode 345. Reverse Vowels of a String 题解
    LeetCode 344. Reverse String 题解
    LeetCode 27. Remove Element 题解
    LeetCode 61. Rotate List 题解
    LeetCode 19.Remove Nth Node From End of List 题解
    Android耗电量
    Android 使用adb查看和修改电池信息
    Android AOP AspectJ 插桩
    Flask相关用法
    Monkey日志信息的11种Event percentage
  • 原文地址:https://www.cnblogs.com/sharing-java/p/10808206.html
Copyright © 2011-2022 走看看