红黑树的性质及添加一个节点到红黑树的步骤及代码实现

红黑树的概念

  • 红黑树是一种含有红黑节点并且能够自平衡的二叉查找树

红黑树的五大性质(重点):

1、 每个节点要么是黑色,要么是红色;
2、 根节点是黑色;
3、 每个叶子节点都是(NIL)都是黑色的(都是null,虚拟出来的);
4、 每个红色节点的两个子节点一定是黑色的;
5、任意一节点到每个叶子节点的路径都包含数量相同的黑节点(黑色完美平衡)。

红黑树的添加:

1、 红黑树的添加分为两个步骤:

  • 第一,因为红黑树本身也是二叉查找树,所以我们每次插入节点首先按二叉查找树的插入方法进行插入;
  • 第二,因为为了满足红黑树的性质,每次插入后,都需要进行修正,使它满足红黑树的五大性质。

2、红黑树的插入节点我们都是默认用红色节点插入,原因如下:

  • 首先,我们看一下红黑树的五大性质,当我们插入的是红色节点时,
    1)性质1:不会违背
    2)性质2:如果根节点不存在,只需要把插入节点作为根节点,把颜色变为黑色即可,操作成本低;
    3)性质3:叶子节点是虚拟出来的null节点,插入的是红色对性质3没有影响;
    4)性质4:这个有可能会违背
    5)性质5:因为我们插入的是红色,不会影响性质5黑色节点的数量,不会违背。

  • 总结:从上述来说,插入的节点是红色不会影响性质5,我们在进行修正的时候就不需要考虑多一条性质,这样操作就会比较简单。

3、上面讲了,添加的步骤总体分为两步:按二叉查找树的性质添加+修正。

3.1.二叉查找树的添加相信大家都会,等下只在代码中实现。
3.2.我主要来讲修正的过程.
  • 首先,我需要先声明一下,我下面用的一些单词的含义:

  • 红黑树自平衡的最小单元
    概念:就是每次操作自平衡时需要操作的节点

  • 修正的过程如下图:
    在这里插入图片描述

3、 上面的情况很容易记忆,接下来详细解释上面四种情况。

1)当红黑树为空时,插入的节点就作为根节点

  • 情况1:需要把颜色变为黑色,满足红黑树性质2;

2)当红黑树不为空时,则需要判断当前节点C的父节点P的颜色

  • 情况2:如果父节点P的颜色为黑色,则可以直接插入,不会违背红黑树的性质;

2.1)如果父节点P的颜色为红色,则违背了性质4,这时候就需要进行修正了,这时需要判断当前节点的叔叔节点U的颜色

  • 情况3:当叔叔节点为红色时,这时需要将父节点P和叔叔节点U都变为黑色,祖父节点G变为红色,因为祖父节点G的颜色变为红色,这时,需要把祖父节点G作为新的当前节点安装情况1,2,3,4进行递归处理;
  • 情况4:当叔叔节点为空(默认黑色),或者叔叔节点颜色为黑色时,需要分成下面两种情况:
  • 情况4.1:CPG三点一线(当前节点,父节点,祖父节点在一条直线上,如果不懂,看下面图)
    在这里插入图片描述
    处理:(左旋)以P为圆心,旋转G点
//左旋的函数,参数是祖父节点G
 private void leftRotation(RBTNode<T> G){
        RBTNode<T> P = G.right;     //得到父节点P 
        //当G为根节点时
        if(G==mRoot){       
            G.right = P.left;
            P.left = G;
            mRoot = P;
        }
        //当G不为根节点时,分为两种情况
        //情况1:G是G的父节点的左孩子,
        //情况2:G是G的父节点的右孩子,
        else {
            G.right = P.left;
            P.left = G;
            //情况1
            if(G==G.parent.left){
                G.parent.left = P;
            }
            //情况2
            else {
                G.parent.right = P;
            }
            P.parent=G.parent;
            G.parent = P;
        }
        //变色操作
        boolean tempColor = G.color;
        G.color = P.color;
        P.color = tempColor;
    }

总结情况4.1:我上面只是列举了左旋的情况,右旋同理,【代码见】

  • 情况4.2:CPG三角关系(看图)

在这里插入图片描述

处理:**需要把它编程直线关系,再按情况4.1处理,以C为圆心,旋转P点,
结果如图:
在这里插入图片描述
总结情况4.2:上面就是展现把三角关系变成直线关系,再进行左旋,或者右旋就可以再次变成一个二叉树了。

总结情况1,2,3,4:上面4种情况涵盖了红黑树的所有添加操作。

代码实现:

import org.junit.Test;

/**
 *java语言红黑树添加操作
 * @param <T>
 */
public class RBTree<T extends Comparable<T>> {
    /**
     * 一个红黑树必须要有根节点
     */
    private RBTNode<T> mRoot;
    //设置两个全局颜色变量,false代表红色,true代表黑色
    private static final boolean RED = false;
    private static final boolean BLACK = true;
    //RBTree的构造方法,根节点初始值为空
    public RBTree() {
        this.mRoot = null;
    }

    /**
     * 1.红黑树必须要有节点,首先定义一个节点类(内部类)
     *     1.1.节点必须包括下面几个属性
     *     节点的颜色
     *     节点的值
     *     节点的父节点
     *     节点的左子节点
     *     节点的右子节点
     */

    public class RBTNode<T extends Comparable<T>>{
        boolean color;              //颜色
        T key;                      //节点值
        RBTNode<T> left;            //左子节点
        RBTNode<T> right;           //右子节点
        RBTNode<T> parent;          //父节点

        //带参构造方法
        public RBTNode(boolean color, T key, RBTNode<T> left, RBTNode<T> right, RBTNode<T> parent) {
            this.color = color;
            this.key = key;
            this.left = left;
            this.right = right;
            this.parent = parent;
        }

        //获取节点的值
        public T getKey() {
            return key;
        }
        //设置遍历的时候输出的内容,值+颜色
        @Override
        public String toString() {
            return "RBTNode{" +
                    "key=" + key +
                    "  color=" + (this.color==RED?"R":"B")+
                    '}';
        }
    }
    /**
     * 2.我们要进行添加,因为红黑树也是二叉查找树,所以:
     *  2.1添加的时候先按二叉查找树的方法添加,
     *  2.2添加完再修正
     */

    /**
     * 2.1
     *  1)插入时,先找到我们要插入的节点的父节点,
     *  2)如果父节点为空,则表明该树还没有插值,就把值付给根节点
     *    如果父节点不为空,则比较大小判断是插在父节点的左子树位置还是右子树位置。
     * @param node     //插入的节点
     */
    private void insert(RBTNode<T> node){
        int cmp;
        RBTNode<T> y = null;    //找到插入节点的父节点
        RBTNode<T> x = mRoot;   //根节点
        //当根节点不为空时,找到插入节点的父节点
        while(x != null){
            y = x;
            cmp = node.key.compareTo(x.key);
            if(cmp<0){
                x=x.left;
            }
            else {
                x=x.right;
            }
        }
        node.parent = y;
        //找到父节点后,判断大小将节点添加进去
        if(y!=null){
            cmp = node.key.compareTo(y.key);
            if(cmp<0){
                y.left = node;
            }
            else {
                y.right = node;
            }
        }
        //当根节点为空时,直接将该节点作为根节点
        else{
            mRoot = node;
        }
        insertFixUp(node);

    }
    //测试方法调用,提供一个T key,调用insert(RBTNode<T> node)插入节点
    public void insert(T key){
        //将插入的节点设置为红色,(红黑树默认插入节点为红色,只违反一条规则)
        RBTNode<T> node = new RBTNode<T>(RED,key,null,null,null);
        if(node !=null){
            insert(node);
        }
    }
    /**
     * 右旋
     * @Param G 祖父节点
     */
    private void rightRotation(RBTNode<T> G){
        RBTNode<T> P = G.left;
        //当祖父节点为根节点时
        if(G==mRoot){
            G.left = P.right;
            P.right = G;
            mRoot = P;
        }
        //当祖父节点不为根节点时
        else{
            G.left = P.right;
            P.right = G;
            if(G==G.parent.left){
                G.parent.left = P;
            }
            else {
                G.parent.right = P;
            }
            P.parent=G.parent;
            G.parent = P;
        }
        //变色操作
        boolean tempColor = G.color;
        G.color = P.color;
        P.color = tempColor;
    }
    /**
     * 左旋
     */
    private void leftRotation(RBTNode<T> G){
        RBTNode<T> P = G.right;
        if(G==mRoot){
            G.right = P.left;
            P.left = G;
            mRoot = P;
        }
        else {
            G.right = P.left;
            P.left = G;
            if(G==G.parent.left){
                G.parent.left = P;
            }
            else {
                G.parent.right = P;
            }
            P.parent=G.parent;
            G.parent = P;
        }
        //变色操作
        boolean tempColor = G.color;
        G.color = P.color;
        P.color = tempColor;
    }
    /**
     * 2.2红黑树插入的修正函数(变色,自旋,自平衡)
     *  1)红黑树自平衡的最小单元是GPC
     *      G:GrandFather 祖父节点
     *      P:Parent 父节点
     *      C:Current 当前插入的节点
     *  2)定义这三个节点:
     *      1)@Param node 当前节点
     *      2)自己定义 G    祖父节点
     *      3)自己定义 P    父节点
     *      4)自己定义 U    叔叔节点  Uncle
     */
    private void insertFixUp(RBTNode<T> C){
        RBTNode<T> G = null;      //祖父节点
        RBTNode<T> P;             //父节点
        RBTNode<T> U = null;      //叔叔节点
        //得到插入节点的父节点
        P = C.parent!=null?C.parent:null;
        //得到插入节点的祖父节点
        if(P!=null){
            G = P.parent!=null?P.parent:null;
        }
        //得到插入节点的叔叔节点
        if(G!=null){
            U = G.left==P?G.right:G.left;
        }
        //情况1
        if(C==mRoot){
            mRoot.color = BLACK;
        }
        //情况2
        else if(C.parent.color==BLACK){
            //不用操作
        }
        //情况3
        else if(U!=null&&P.color==RED && U.color==RED){
                G.color = RED;
                P.color = BLACK;
                U.color = BLACK;
                //因为G节点变为红色了,所以需要把G节点作为新的插入节点递归调整
                insertFixUp(G);
        }
        //情况4
        else{
            //父节点为祖父节点的左孩子
            if(P == G.left){
                //当前节点为父节点的左孩子,CPG三点一线的情况
                if(C == P.left){
                   //右旋
                    rightRotation(G);
                }
                //CPG三角关系
                else{
                    //先变为三点一线
                    G.left = C;
                    C.parent=G;
                    C.left = P;
                    P.parent = C;
                    P.right = null;
                    //再右旋
                    rightRotation(G);
                }
            }
            //父节点为祖父节点的右孩子
            else{
                //当前节点为父节点的右孩子,CPG三点一线的情况
                if(C == P.right){
                    //左旋
                    leftRotation(G);
                }
                //CPG三角关系
                else {
                    //先变三角为直线
                    G.right = C;
                    C.parent = G;
                    C.right = P;
                    P.parent = C;
                    P.left = null;
                    //再左旋
                    leftRotation(G);
                }
            }
        }
    }

    /**
     * 中序遍历
     */
    private void inOrder(RBTNode<T> tree){
        if(tree!=null){
            inOrder(tree.left);
            System.out.print(tree.key+" ");
            inOrder(tree.right);
        }
    }
    public void inOrder(){
        inOrder(this.mRoot);
    }

    /**
     * 前序遍历
     */
    private void preOrder(RBTNode<T> tree){
        if(tree!=null){
            System.out.print(tree.key+" ");
            preOrder(tree.left);
            preOrder(tree.right);
        }
    }
    public void preOrder(){
        preOrder(this.mRoot);
    }

    /**
     * 后序遍历
     */
    private void postOrder(RBTNode<T> tree){
        if(tree!=null){
            postOrder(tree.left);
            postOrder(tree.right);
            System.out.print(tree);
        }
    }
    public void postOrder(){
        postOrder(this.mRoot);
    }

    /**
     * 通过键值查找节点
     */
    private RBTNode<T> getByKey(T key){
        int cmp;
        RBTNode<T> x = mRoot;
        while (x!=null){
            cmp = x.key.compareTo(key);
            if(cmp<0){
                x= x.right;
            }
            else if(cmp>0) {
                x=x.left;
            }
            else {
                return x;
            }
        }
        return null;
    }
    @Test
    public void test1(){
        RBTree<Integer> trbTree = new RBTree<>();
        trbTree.insert(60);
        trbTree.insert(70);
        trbTree.insert(80);
        trbTree.insert(90);
        trbTree.insert(100);
        trbTree.insert(140);
        trbTree.insert(130);
        trbTree.insert(110);
        trbTree.insert(105);
        trbTree.insert(120);
        //得到某个节点的情况
        System.out.println(trbTree.getByKey(130).parent);
        System.out.println(trbTree.getByKey(130).left);
        System.out.println(trbTree.getByKey(130).right);
//        trbTree.insert(100);
//        trbTree.insert(90);
//        trbTree.insert(80);
//        trbTree.insert(70);
//        trbTree.insert(60);
//        trbTree.insert(65);
//        trbTree.insert(62);

        trbTree.inOrder();
        System.out.println();
        trbTree.preOrder();
        System.out.println();
        //后续遍历输出了颜色,其他两个我只是输出Key
        trbTree.postOrder();
    }
}

留言:删除还没整理出来,文章如有错误,还望博友们积极指导

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值