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);//将根节点设置为黑色
    }
    

      

      

  • 相关阅读:
    ASP.NET(C#)——唯一订单号
    Oracle——备份与还原
    ASP.NET(C#)——日期函数
    数据安全——数据安全标准
    文件内容的追加
    文件的读取
    创建文件,写文件
    遍历文件改进
    遍历文件夹
    递归方法求前n项和
  • 原文地址:https://www.cnblogs.com/sharing-java/p/10808206.html
Copyright © 2011-2022 走看看