【数据结构和算法 01】红黑树算法详解和java具体实现

      想具体实现以下红黑树算法已经有很长一段时间了,但是一直没有抽出比较完整的时间来系统整理和实现以下红黑树算法,下面一起来看看红黑树的具体实现吧,想想也很简单。

1. 首先介绍以下红黑树的性质

    红黑树是一种满足红黑性质的搜索二叉树:

  1. 红黑树的节点是红色或者黑色
  2. 根节点是黑色的
  3. 每个叶子节点是黑色的(这里的叶子节点是指空节点)
  4. 每个红色节点的孩子节点都是黑色的
  5. 每个节点到其后代节点的简单路径上,都包含相同数目的黑色节点

2. 左旋和右旋

    红黑树维持的性质主要通过两个旋转操作(左旋和右旋)、变色实现,变色很容易理解,就是字面意思将节点颜色从一种颜色修改为另外一种,关键是变色的时机和如何变色,比较复杂,这个问题会在后面的实现步骤讲解中涉及,这里主要针对左旋和右旋进行讲解。

因为后面设计代码具体实现,这里首先给出java红黑树节点类的实现。

public class RBNode<T extends Comparable<T>> {
        boolean color;//节点颜色
        T key; //关键字(键值)
        RBNode<T> left;//坐孩子节点
        RBNode<T> right;//右孩子节点
        RBNode<T> parent;//父节点


        //节点构造函数
        public RBNode(T key, boolean color, RBNode<T> parent, RBNode<T> left, RBNode<T> right) {
            this.key = key;
            this.color = color;
            this.parent = parent;
            this.left = left;
            this.right = right;
        }

        public T getKey(){
            return key;
        }

        public String toString(){
            return ""+key+":"+(this.color == true? "R":"B");
        }
    }

左旋

     左旋的意思就是以一个节点为轴,向左旋转,将右节点的左子树作为该节点的右字树连接到该节点上,该节点的右节点作为该节点的父节点替换轴节点原来的位置,如下图所示:

 

左旋代码具体实现:

 /**
     * 节点左旋
     * @param x 旋转时作为轴的节点
     *
     *        X                     Y
     *     /    \                /    \
     *    A     Y     ->        X     RB
     *   / \   / \            /  \
     *  LA RA LB  RB         A  LB
     *                      / \
     *                     LA RA
     *
     * 节点左旋的主要工作:
     * 1.x节点的左孩子变为y节点的右孩子,y左孩子的父节点变为了x
     * 2.x节点变为y节点的左孩子,y变为x父节点的子节点
     * 3.x变为了y的子孩子,x的父节点变为y
     * 每个节点的父节点,左右孩子节点都要注意维护
     */
    private void leftRotate(RBNode<T> x){
        //将x节点的右孩子修改为y节点的左孩子
        RBNode<T> y = x.right;
        x.right = y.left;

        //如果y的左孩子不是null,修改左孩子的父节点,左孩子其他节点不需要变化
        if(y.left != null)
            y.left.parent = x;

        //y节点的父节点修改为x的父节点,左孩子变为了x,右孩子不需要修改
        y.parent = x.parent;

        if(x.parent == null)
            //如果x的父节点为null,则x原来为根节点,现在根节点修改为y
            this.root = y;
        else {
            //判断x是父节点的左孩子还是右孩子,为父节点的子节点赋值
            if(x == x.parent.left)
                x.parent.left = y;
            else
                x.parent.right = y;
        }

        y.left = x;
        //x的父节点修改为y
        x.parent = y;

    }

右旋

    右旋的机制和左旋相同,只改变了旋转的方向,如下图所示。


节点右旋实现:

左旋右旋很好理解,关键是节点位置变化后,需要细心维护节点的父亲和孩子节点的变化。

/**
     * 节点右旋
     * @param x 旋转时作为轴的节点
     *
     *        |                     |
     *        X                     Y
     *     /    \                /    \
     *    Y     B     ->        LA     X
     *   / \   / \                   /  \
     *  LA RA LB  RB                RA  B
     *                                 / \
     *                                LB RB
     *
     * 节点左旋的主要工作:
     * 1.y节点的右孩子变为x节点的左孩子,y右孩子的父节点变为了x
     * 2.x节点变为y节点的右孩子,x变为y父节点的子节点
     * 3.x变为了y的子孩子,x的父节点变为y
     * 每个节点的父节点,左右孩子节点都要注意维护
     */
    private void rightRotate(RBNode<T> x){
        //将x节点的左孩子修改为y节点的右孩子
        RBNode<T> y = x.left;
        x.left = y.right;

        //如果y的右孩子不是null,修改右孩子的父节点,右孩子其他节点不需要变化
        if(y.right != null)
            y.right.parent = x;

        //y节点的父节点修改为x的父节点,右孩子变为了x,左孩子不需要修改
        y.parent = x.parent;


        if(x.parent == null)
            //如果x的父节点为null,则x原来为根节点,现在根节点修改为y
            this.root = y;
        else {
            //判断x是父节点的左孩子还是右孩子,为父节点的子节点赋值
            if(x == x.parent.left)
                x.parent.left = y;
            else
                x.parent.right = y;
        }

        y.right = x;
        //x的父节点修改为y
        x.parent = y;

    }

3. 红黑树的插入操作

    红黑树的插入操作主要分为以下几个步骤:

  1. 找到插入节点的位置(这里位置的寻找和二叉搜索树的方式相同)
  2. 插入成功后对红黑树的性质进行维护调整
以下代码是查找插入位置的代码具体实现:
 public void insert(T key){
        RBNode<T> node = new RBNode<>(key,RED,null,null,null);
        if(node != null){
            insertNode(node);
        }
    }

    private void insertNode(RBNode<T> node){

        //首先需要找到插入节点的位置,这里和二叉搜索树的过程是一样的
        //从根节点开始遍历
        RBNode<T> cur = this.root;
        RBNode<T> p = null;
        while (cur != null){
            //记录最后插入位置的父节点
            p = cur;

            int cmp = node.getKey().compareTo(cur.key);
            if(cmp >0){
                cur = cur.right;
            }else {
                cur = cur.left;
            }
        }

        //修改node的父节点
        node.parent = p;

        //判断node是插在左节点还是右节点
        if(p != null){
            int cmp = node.getKey().compareTo(p.key);

            if(cmp < 0){
                p.left = node;
            }else {
                p.right = node;
            }
        }else {
            this.root = node;
        }


        //插入之后对红黑树进行调整
        insertFixUp(node);

    }

      以上代码比较容易理解,最后主要是讲解insertFixUp(node)函数对红黑树的性质进行维护,通过以上代码我们可以发现新插入节点是红色节点,那么来分析一下什么情况会破坏红黑树的性质,首先如果插入节点的父节点是黑色的,那么很理想,不需要对红黑树进行调整,如果父节点为红色,那么很遗憾与红黑树的性质发生了冲突,所以首要前提是插入节点父节点为红色节点。

      在满足父节点为红色的情况下,分为以下三种情况需要对红黑树进行不同的变色和旋转调整(以下情况的前提是插入节点的父节点为左孩子):

  1. 插入节点(z)的叔叔节点(y)是红色的:这种情况只要把z.parent和y修改为黑色,这样解决了z和z.parent都是红色的问题,同时把z.p.p变为红色,这样保证了树的黑色高度不变,同时将z修改为z.p.p来继续检查。这样操作后,z可能满足1,2,3中的任意一种情况,或者直接z.p为黑色节点直接退出。
  2. 插入节点(z)的叔叔节点(y)是黑色(或者叔叔节点不存在)并且z是一个右孩子:此时以z.parent为轴左旋,并且z和parent互换,此时的z和z.parent还是都为红色,这是z的特征正好满足3的情况。
  3. 插入节点(z)的叔叔节点(y)是黑色(或者叔叔节点不存在)并且z是一个左孩子:这时把z.parent修改为黑色,z.p.p修改为红色,再以z.p.p为轴右旋,因为y为黑色,所以并不会产生颜色冲突,同时替换z.p.p的和z.p同为黑色,也不会影响高度(因为z.p开始时是红色才需要调整,所以z.p.p一定是黑色)。

红黑树插入调整操作

private void insertFixUp(RBNode<T> node){

        RBNode<T> parent,gparent;


        while ((parent = node.parent) != null && isRed(parent)){

            gparent = parent.parent;

            //如果父节点为左孩子,下面else相反
            if(parent == gparent.left) {

                //case1 :父节点和叔叔节点都是红色的
                //将父节点和叔叔节点都变成黑色,祖父节点变成红色,node修改为祖父节点
                //判断是否与叔叔节点
                if ((gparent.right != null) && isRed(gparent.right)) {
                    parent.color = BLACK;
                    gparent.right.color = BLACK;
                    gparent.color = RED;
                    node = gparent;
                    continue; //回到while重新判断
                }

                //case2: 父节点是红色,叔叔节点是黑色或者不存在,并且当前节点为右孩子
                if( node == parent.right){
                    leftRotate(parent);
                    RBNode<T> temp = parent;
                    parent = node;
                    node = temp;//此处结束后,一定会进入case3
                }

                //case3: 父节点是红色,叔叔节点是黑色,并且当前节点是左孩子
                parent.color = BLACK;
                gparent.color = RED;
                rightRotate(gparent);

            } else {
                //case1 :父节点和叔叔节点都是红色的
                //将父节点和叔叔节点都变成黑色,祖父节点变成红色,node修改为祖父节点

                if ((gparent.left != null) && isRed(gparent.left)) {
                    parent.color = BLACK;
                    gparent.left.color = BLACK;
                    gparent.color = RED;
                    node = gparent;
                    continue; //回到while重新判断
                }

                //case2: 父节点是红色,叔叔节点是黑色,并且当前节点为左孩子
                if(node == parent.left){
                    rightRotate(parent);
                    RBNode<T> temp = parent;
                    parent = node;
                    node = temp;//此处结束后,一定会进入case3
                }

                //case3: 父节点是红色,叔叔节点是黑色,并且当前节点是左孩子
                parent.color = BLACK;
                gparent.color = RED;
                leftRotate(gparent);

            }

        }

        this.root.color = BLACK;

    }

4.  红黑树删除操作

    红黑树删除操作理解起来非常简单,主要是删除节点后需要对红黑树的性质进行维护,删除节点主要分为以下几种情况:

  1. 如果删除节点没有孩子节点,那么直接删除即可。
  2. 如果删除节点有一个子孩子,那么用子节点替换删除节点。
  3. 如果删除节点有两个子孩子,那么首先需要找到删除节点的后继节点(本例子中后继节点为右子树中的最小节点,所以后继节点一定没有左孩子),用后继节点替换删除节点(包括颜色也要保持一致),这样删除节点的位置保持了原来的红黑树性质,这样问题转化为了删除后继节点,即变成了1,2两种情况。
    public void remove(T key) {
        RBNode<T> node;
        RBNode<T> x = this.root;

        while (x!=null){
            int cmp = key.compareTo(x.key);
            if(cmp == 0){
                removeNode(x);
                return;
            }else if(cmp < 0){
                x = x.left;
            }else {
                x = x.right;
            }
        }
    }


    /**
     * 让节点v替换节点u的位置,并且维护u父节点与v的父子关系
     * @param u
     * @param v
     */
    private void transpalnt(RBNode<T> u,RBNode<T> v){

        if(u.parent == null){
            this.root = v;
        }
        else if(u == u.parent.left){
            u.parent.left = v;
        }else
            u.parent.right = v;

        if(v != null)
            v.parent = u.parent;

    }

    /**
     * 获得节点node开始子树的最小节点
     * @param node
     * @return
     */
    private RBNode<T> treeMinNode(RBNode<T> node){

        RBNode<T> temp = node;
        while (temp.left != null){
            temp = temp.left;
        }

        return temp;
    }

    /**
     * 删除节点主要需要找到后继节点来替换当前节点
     * 1.如果节点没有子节点,直接删除就可以
     * 2.如果节点只有一个子节点,直接删除,让子节点顶替它
     * 3.如果有两个字节点,就需要找到后继节点来替换它
     * @param node
     */

    private void removeNode(RBNode<T> node){

        RBNode<T> y = node;
        boolean color = y.color; //记录最后删除的节点y的颜色

        RBNode<T> x;//记录最后需要调整的节点

        //node左孩子为空,或者没有子节点,这样直接用右孩子替换node
        if(node.left == null){
            //x节点记录右孩子
            x = node.right;
            transpalnt(node,node.right);

        }else if(node.right == null){  //node只有左孩子
            //x节点记录左孩子
            x = node.left;
            transpalnt(node,node.left);
        }else {
            //有两个子节点,y为node节点的后继节点
            y = treeMinNode(node.right);
            //记录后继节点的颜色
            color = y.color;
            //此时y节点肯定没有左孩子,x记录y节点的有孩子
            x = y.right;

            //如果node的后继就是右孩子,此时就是x的父节点就是y
            if(y.parent == node){
                x.parent = y;
            }else {
                //y子节点肯定少于两个,问题转化为了直接删除一个少于两个子节点的问题
                //让y替换掉node的位置
                transpalnt(y,y.right);
                y.right = node.right;
                y.right.parent = y;
            }
            //将节点y替换节点node,修改一下y节点的父节点
            transpalnt(node,y);
            //以上操作已经对y的右节点进行了维护
            y.left = node.left;
            y.left.parent = y;
            y.color = node.color;

        }

        //如果删除的y节点的颜色是黑色的,这样就破坏了删除节点的平衡
        if(color == BLACK){
            removeFixUp(x);
        }

    }

       以上是完成了节点的删除操作,那么下一步需要完成对红黑树的性质维护,那么什么情况下破坏了树的性质呢,首先如果删除节点y的子孩子少于两个,那么如果y的颜色为黑色,则会影响树的性质;如果y的子孩子为两个的话,那么y表示删除的后继节点,那么就需要看后继节点(y)的颜色,如果后继节点的颜色为黑色,则破坏了红黑输的性质。这里去删除节点(y)的后继为x,如果x为红色的话,那么直接将x的颜色变为黑色即可;如果x变为了根节点,那么删除的y一定是根节点并且没有左孩子,并且x为y的右孩子且为红色。

      那么删除节点后红黑树的调整主要分为以下几种情况:

  1. 如果x的兄弟节点(w)的颜色为红色:则w的父节点和子孩子都是黑色,这时将w的颜色变为黑色,x父节点颜色变为红色,并且以x的父节点为轴进行左旋,经过1后,变为case2,3,4的情况。
  2. 如果x的兄弟节点w为黑色,并且w的两个子节点也是黑色的:那么将w改为红色,x的位置修改为x的父节点。这里可能会破坏红黑树的性质,但是如果最后x不符合标准后,最后会把x染成黑色,如果x为黑色的话,则继续进行判断。
  3. 如果w节点的颜色为黑色,w的左孩子为红色,w的右孩子为黑色:这时将w变为红色,w的左孩子变为黑色,并且以w为轴进行右旋(这里情况判断的前提是x的为左孩子),这样情况变为了和情况4相同的情况。
  4. 如果w的颜色为黑色,并且w的右孩子为红色(左孩子随意):这时候把w的颜色变为变为x父节点的颜色,父节点的颜色变为黑色,w的右孩子节点变为黑色,然后以父节点为轴左旋,将x赋值为root节点,结束操作。

    这里再对以上操作进行总结一下:

  1. 操作一结束后,红黑树原来的高度变化并没有发生改变,只是高度差别改变成了x和新的w子树之间的差距;
  2. 操作二结束后,如果新的x为红色的话,这样的直接将x变为黑色,这样x所在的子树黑度加1,弥补了删除节点的黑色高度;如果x为黑色的话,则继续进行判断,回到最初的情况。
  3. 操作三结束后,原来w所在的子树高度并没有发生变化,并且一定符合情况四;
  4. 操作四结束后,w颜色为原来父节点的颜色,所以新的结构中右子树高度不变,而把左子树的黑色高度加1,弥补了删除节点之后的操作。
删除调整代码:
 /**
     * 1.如果x为根节点(也就是删除节点为根节点,并且子节点孩子少于2个)若x为红色,直接变成黑色就好了
     *
     * @param x
     */
    private void removeFixUp(RBNode<T> x){

        RBNode<T> w;
        //如果x为跟节点,那么删除的y一定是根节点,并且没有左孩子且x为根节点的右孩子,且x为红色
        while (( x != this.root) && (x == null || x.color == BLACK)){
            if(x == x.parent.left){
                //x的兄弟节点w
                w = x.parent.right;
                //case1: 如果w的颜色为红色,则w的父节点和子孩子都是黑色,经过1后,变为case2,3,4的情况
                if(w.color == RED){
                    w.color = BLACK;
                    x.parent.color = RED;
                    leftRotate(x.parent);
                    w = x.parent.right;
                }
                //case2: w节点的两个子节点都是黑色
                if((w.left == null || w.left.color == BLACK) && (w.right == null ||w.right.color==BLACK)){
                    w.color = RED;
                    x = x.parent;//如果x为红色,这时候就会结束while,最后为x着色黑色
                    //如果这里x和右孩子都是红色的违反红黑树性质,最后的时候(while结束后)会把x设置为黑色
                }
                //case3: w节点的左节点都是红色,右孩子是黑色
                else if( w.right == null || w.right.color==BLACK){
                    w.left.color = BLACK;
                    w.color = RED;
                    rightRotate(w);
                    w = x.parent.right;
                    //此时情形和case4 相同

                    //case4:w节点的右节点是红色,左节点任意颜色
                    w.color = x.parent.color;
                    x.parent.color = BLACK;
                    w.right.color = BLACK;
                    leftRotate(x.parent);
                    x = this.root;
                    break;//跳出while
                }
            }else {//对称操作

                //x的兄弟节点w
                w = x.parent.left;
                //case1: 如果w的颜色为红色,则w的父节点和子孩子都是黑色,经过1后,变为case2,3,4的情况
                if(w.color == RED){
                    w.color = BLACK;
                    x.parent.color = RED;
                    rightRotate(x.parent);
                    w = x.parent.left;
                }
                //case2: w节点的两个子节点都是黑色
                if(w.left.color == BLACK && w.right.color==BLACK){
                    w.color = RED;
                    x = x.parent;//如果x为红色,这时候就会结束while,最后为x着色黑色
                    //如果这里x和右孩子都是红色的违反红黑树性质,最后的时候(while结束后)会把x设置为黑色
                }
                //case3: w节点的左节点都是红色,右孩子是黑色
                else if( w.left.color==BLACK){
                    w.right.color = BLACK;
                    w.color = RED;
                    leftRotate(w);
                    w = x.parent.left;
                    //此时情形和case4 相同

                    //case4:w节点的右节点都是红色,左节点任意颜色
                    w.color = x.parent.color;
                    x.parent.color = BLACK;
                    w.left.color = BLACK;
                    rightRotate(x.parent);
                    x = this.root;
                    break;//跳出while
                }
            }
        }

        x.color = BLACK;
    }

全部代码

package com.javaSource.learning;

import com.sun.org.apache.regexp.internal.RE;

import java.util.ArrayDeque;

public class RBTree<T extends Comparable<T>> {

    public static final boolean RED = true;
    public static final boolean BLACK = false;
    private RBNode<T> root; //根节点

    public class RBNode<T extends Comparable<T>> {
        boolean color;//节点颜色
        T key; //关键字(键值)
        RBNode<T> left;//坐孩子节点
        RBNode<T> right;//右孩子节点
        RBNode<T> parent;//父节点


        //节点构造函数
        public RBNode(T key, boolean color, RBNode<T> parent, RBNode<T> left, RBNode<T> right) {
            this.key = key;
            this.color = color;
            this.parent = parent;
            this.left = left;
            this.right = right;
        }

        public T getKey(){
            return key;
        }

        public String toString(){
            return ""+key+":"+(this.color == true? "R":"B");
        }
    }

    public RBTree(){
        root = null;
    }
    public boolean isRed(RBNode<T> node){
        return node.color;
    }

    public boolean isBlack(RBNode<T> node){
        return !node.color;
    }


    /**
     * 节点左旋
     * @param x 旋转时作为轴的节点
     *
     *        X                     Y
     *     /    \                /    \
     *    A     Y     ->        X     RB
     *   / \   / \            /  \
     *  LA RA LB  RB         A  LB
     *                      / \
     *                     LA RA
     *
     * 节点左旋的主要工作:
     * 1.x节点的左孩子变为y节点的右孩子,y左孩子的父节点变为了x
     * 2.x节点变为y节点的左孩子,y变为x父节点的子节点
     * 3.x变为了y的子孩子,x的父节点变为y
     * 每个节点的父节点,左右孩子节点都要注意维护
     */
    private void leftRotate(RBNode<T> x){
        //将x节点的右孩子修改为y节点的左孩子
        RBNode<T> y = x.right;
        x.right = y.left;

        //如果y的左孩子不是null,修改左孩子的父节点,左孩子其他节点不需要变化
        if(y.left != null)
            y.left.parent = x;

        //y节点的父节点修改为x的父节点,左孩子变为了x,右孩子不需要修改
        y.parent = x.parent;

        if(x.parent == null)
            //如果x的父节点为null,则x原来为根节点,现在根节点修改为y
            this.root = y;
        else {
            //判断x是父节点的左孩子还是右孩子,为父节点的子节点赋值
            if(x == x.parent.left)
                x.parent.left = y;
            else
                x.parent.right = y;
        }

        y.left = x;
        //x的父节点修改为y
        x.parent = y;

    }


    /**
     * 节点右旋
     * @param x 旋转时作为轴的节点
     *
     *        |                     |
     *        X                     Y
     *     /    \                /    \
     *    Y     B     ->        LA     X
     *   / \   / \                   /  \
     *  LA RA LB  RB                RA  B
     *                                 / \
     *                                LB RB
     *
     * 节点左旋的主要工作:
     * 1.y节点的右孩子变为x节点的左孩子,y右孩子的父节点变为了x
     * 2.x节点变为y节点的右孩子,x变为y父节点的子节点
     * 3.x变为了y的子孩子,x的父节点变为y
     * 每个节点的父节点,左右孩子节点都要注意维护
     */
    private void rightRotate(RBNode<T> x){
        //将x节点的左孩子修改为y节点的右孩子
        RBNode<T> y = x.left;
        x.left = y.right;

        //如果y的右孩子不是null,修改右孩子的父节点,右孩子其他节点不需要变化
        if(y.right != null)
            y.right.parent = x;

        //y节点的父节点修改为x的父节点,右孩子变为了x,左孩子不需要修改
        y.parent = x.parent;


        if(x.parent == null)
            //如果x的父节点为null,则x原来为根节点,现在根节点修改为y
            this.root = y;
        else {
            //判断x是父节点的左孩子还是右孩子,为父节点的子节点赋值
            if(x == x.parent.left)
                x.parent.left = y;
            else
                x.parent.right = y;
        }

        y.right = x;
        //x的父节点修改为y
        x.parent = y;

    }


    public void insert(T key){
        RBNode<T> node = new RBNode<>(key,RED,null,null,null);
        if(node != null){
            insertNode(node);
        }
    }

    private void insertNode(RBNode<T> node){

        //首先需要找到插入节点的位置,这里和二叉搜索树的过程是一样的
        //从根节点开始遍历
        RBNode<T> cur = this.root;
        RBNode<T> p = null;
        while (cur != null){
            //记录最后插入位置的父节点
            p = cur;

            int cmp = node.getKey().compareTo(cur.key);
            if(cmp >0){
                cur = cur.right;
            }else {
                cur = cur.left;
            }
        }

        //修改node的父节点
        node.parent = p;

        //判断node是插在左节点还是右节点
        if(p != null){
            int cmp = node.getKey().compareTo(p.key);

            if(cmp < 0){
                p.left = node;
            }else {
                p.right = node;
            }
        }else {
            this.root = node;
        }


        //插入之后对红黑树进行调整
        insertFixUp(node);

    }

    private void insertFixUp(RBNode<T> node){

        RBNode<T> parent,gparent;


        while ((parent = node.parent) != null && isRed(parent)){

            gparent = parent.parent;

            //如果父节点为左孩子,下面else相反
            if(parent == gparent.left) {

                //case1 :父节点和叔叔节点都是红色的
                //将父节点和叔叔节点都变成黑色,祖父节点变成红色,node修改为祖父节点
                //判断是否与叔叔节点
                if ((gparent.right != null) && isRed(gparent.right)) {
                    parent.color = BLACK;
                    gparent.right.color = BLACK;
                    gparent.color = RED;
                    node = gparent;
                    continue; //回到while重新判断
                }

                //case2: 父节点是红色,叔叔节点是黑色或者不存在,并且当前节点为右孩子
                if( node == parent.right){
                    leftRotate(parent);
                    RBNode<T> temp = parent;
                    parent = node;
                    node = temp;//此处结束后,一定会进入case3
                }

                //case3: 父节点是红色,叔叔节点是黑色,并且当前节点是左孩子
                parent.color = BLACK;
                gparent.color = RED;
                rightRotate(gparent);

            } else {
                //case1 :父节点和叔叔节点都是红色的
                //将父节点和叔叔节点都变成黑色,祖父节点变成红色,node修改为祖父节点

                if ((gparent.left != null) && isRed(gparent.left)) {
                    parent.color = BLACK;
                    gparent.left.color = BLACK;
                    gparent.color = RED;
                    node = gparent;
                    continue; //回到while重新判断
                }

                //case2: 父节点是红色,叔叔节点是黑色,并且当前节点为左孩子
                if(node == parent.left){
                    rightRotate(parent);
                    RBNode<T> temp = parent;
                    parent = node;
                    node = temp;//此处结束后,一定会进入case3
                }

                //case3: 父节点是红色,叔叔节点是黑色,并且当前节点是左孩子
                parent.color = BLACK;
                gparent.color = RED;
                leftRotate(gparent);

            }

        }

        this.root.color = BLACK;

    }


    public void remove(T key) {
        RBNode<T> node;
        RBNode<T> x = this.root;

        while (x!=null){
            int cmp = key.compareTo(x.key);
            if(cmp == 0){
                removeNode(x);
                return;
            }else if(cmp < 0){
                x = x.left;
            }else {
                x = x.right;
            }
        }
    }


    /**
     * 让节点v替换节点u的位置,并且维护u父节点与v的父子关系
     * @param u
     * @param v
     */
    private void transpalnt(RBNode<T> u,RBNode<T> v){

        if(u.parent == null){
            this.root = v;
        }
        else if(u == u.parent.left){
            u.parent.left = v;
        }else
            u.parent.right = v;

        if(v != null)
            v.parent = u.parent;

    }

    /**
     * 获得节点node开始子树的最小节点
     * @param node
     * @return
     */
    private RBNode<T> treeMinNode(RBNode<T> node){

        RBNode<T> temp = node;
        while (temp.left != null){
            temp = temp.left;
        }

        return temp;
    }

    /**
     * 删除节点主要需要找到后继节点来替换当前节点
     * 1.如果节点没有子节点,直接删除就可以
     * 2.如果节点只有一个子节点,直接删除,让子节点顶替它
     * 3.如果有两个字节点,就需要找到后继节点来替换它
     * @param node
     */

    private void removeNode(RBNode<T> node){

        RBNode<T> y = node;
        boolean color = y.color; //记录最后删除的节点y的颜色

        RBNode<T> x;//记录最后需要调整的节点

        //node左孩子为空,或者没有子节点,这样直接用右孩子替换node
        if(node.left == null){
            //x节点记录右孩子
            x = node.right;
            transpalnt(node,node.right);

        }else if(node.right == null){  //node只有左孩子
            //x节点记录左孩子
            x = node.left;
            transpalnt(node,node.left);
        }else {
            //有两个子节点,y为node节点的后继节点
            y = treeMinNode(node.right);
            //记录后继节点的颜色
            color = y.color;
            //此时y节点肯定没有左孩子,x记录y节点的有孩子
            x = y.right;

            //如果node的后继就是右孩子,此时就是x的父节点就是y
            if(y.parent == node){
                x.parent = y;
            }else {
                //y子节点肯定少于两个,问题转化为了直接删除一个少于两个子节点的问题
                //让y替换掉node的位置
                transpalnt(y,y.right);
                y.right = node.right;
                y.right.parent = y;
            }
            //将节点y替换节点node,修改一下y节点的父节点
            transpalnt(node,y);
            //以上操作已经对y的右节点进行了维护
            y.left = node.left;
            y.left.parent = y;
            y.color = node.color;

        }

        //如果删除的y节点的颜色是黑色的,这样就破坏了删除节点的平衡
        if(color == BLACK){
            removeFixUp(x);
        }

    }

    /**
     * 1.如果x为根节点(也就是删除节点为根节点,并且子节点孩子少于2个)若x为红色,直接变成黑色就好了
     *
     * @param x
     */
    private void removeFixUp(RBNode<T> x){

        RBNode<T> w;
        //如果x为跟节点,那么删除的y一定是根节点,并且没有左孩子且x为根节点的右孩子,且x为红色
        while (( x != this.root) && (x == null || x.color == BLACK)){
            if(x == x.parent.left){
                //x的兄弟节点w
                w = x.parent.right;
                //case1: 如果w的颜色为红色,则w的父节点和子孩子都是黑色,经过1后,变为case2,3,4的情况
                if(w.color == RED){
                    w.color = BLACK;
                    x.parent.color = RED;
                    leftRotate(x.parent);
                    w = x.parent.right;
                }
                //case2: w节点的两个子节点都是黑色
                if((w.left == null || w.left.color == BLACK) && (w.right == null ||w.right.color==BLACK)){
                    w.color = RED;
                    x = x.parent;//如果x为红色,这时候就会结束while,最后为x着色黑色
                    //如果这里x和右孩子都是红色的违反红黑树性质,最后的时候(while结束后)会把x设置为黑色
                }
                //case3: w节点的左节点都是红色,右孩子是黑色
                else if( w.right == null || w.right.color==BLACK){
                    w.left.color = BLACK;
                    w.color = RED;
                    rightRotate(w);
                    w = x.parent.right;
                    //此时情形和case4 相同

                    //case4:w节点的右节点是红色,左节点任意颜色
                    w.color = x.parent.color;
                    x.parent.color = BLACK;
                    w.right.color = BLACK;
                    leftRotate(x.parent);
                    x = this.root;
                    break;//跳出while
                }
            }else {//对称操作

                //x的兄弟节点w
                w = x.parent.left;
                //case1: 如果w的颜色为红色,则w的父节点和子孩子都是黑色,经过1后,变为case2,3,4的情况
                if(w.color == RED){
                    w.color = BLACK;
                    x.parent.color = RED;
                    rightRotate(x.parent);
                    w = x.parent.left;
                }
                //case2: w节点的两个子节点都是黑色
                if(w.left.color == BLACK && w.right.color==BLACK){
                    w.color = RED;
                    x = x.parent;//如果x为红色,这时候就会结束while,最后为x着色黑色
                    //如果这里x和右孩子都是红色的违反红黑树性质,最后的时候(while结束后)会把x设置为黑色
                }
                //case3: w节点的左节点都是红色,右孩子是黑色
                else if( w.left.color==BLACK){
                    w.right.color = BLACK;
                    w.color = RED;
                    leftRotate(w);
                    w = x.parent.left;
                    //此时情形和case4 相同

                    //case4:w节点的右节点都是红色,左节点任意颜色
                    w.color = x.parent.color;
                    x.parent.color = BLACK;
                    w.left.color = BLACK;
                    rightRotate(x.parent);
                    x = this.root;
                    break;//跳出while
                }
            }
        }

        x.color = BLACK;
    }


    /**
     * 广度遍历红黑树
     * @param
     */
    public void breadthTraverse(){

        ArrayDeque<RBNode<T>> deque = new ArrayDeque<>();
        if(root != null) {
            deque.add(root);
        }
        else return;

        RBNode<T> node;
        while (!deque.isEmpty()){
            node = deque.pop();
            System.out.println(node.key.toString());
            if(node.left !=null)
            deque.add(node.left);
            if(node.right!=null)
            deque.add(node.right);
        }

    }

}

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值