高级数据结构——红黑树的左右旋转、插入删除

1.红黑树的定义:

AVL树在数据量大的情况下,如果进行插入和删除操作,为了维护 AVL树节点的平衡,在插入和删除数据时做了大量的旋转操作,拖慢了进行数据插入删除操作的时间,而红黑树相对二叉树来说,并非绝对的平衡树,它要求左右子树的高度差不超过2倍,故进行旋转操作的次数减少。

/**
 * 红黑树的节点颜色定义
 */
enum Color{
    BLACK,RED;
}

/**
 * 红黑树的节点类型定义
 * @param <T>
 */
class RBNode<T extends Comparable<T>> {
    private T data;
    private RBNode<T> left;
    private RBNode<T> right;
    private RBNode<T> parent;
    private Color color;

    public RBNode(T data, Color color) {
        this.data = data;
        this.color = color;
    }

    public RBNode(T data, RBNode<T> left, RBNode<T> right, RBNode<T> parent, Color color) {
        this.data = data;
        this.left = left;
        this.right = right;
        this.parent = parent;
        this.color = color;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public RBNode<T> getLeft() {
        return left;
    }

    public void setLeft(RBNode<T> left) {
        this.left = left;
    }

    public RBNode<T> getRight() {
        return right;
    }

    public void setRight(RBNode<T> right) {
        this.right = right;
    }

    public RBNode<T> getParent() {
        return parent;
    }

    public void setParent(RBNode<T> parent) {
        this.parent = parent;
    }

    public Color getColor() {
        return color;
    }

    public void setColor(Color color) {
        this.color = color;
    }
}

红黑树的性质如下:
1.每个节点都是由颜色的,不是红色就是黑色
2.root必须是黑色
3.所有叶子节点都是黑色,叶子节点都是null,不存储实际的数据
4.每个红色节点都包含两个黑色子节点,或者说每个叶子节点到根节点的所有路径中,不允许出现两个连续的红色节点
5.从任一节点到其叶子节点的简单路径都包含相同数目的黑色节点
在这里插入图片描述

/**
 * 红黑树的定义
 */
class RBTree<T extends Comparable<T>>{
    private RBNode<T> root;
    /**
     * 获取红黑树节点颜色
     */
    private Color color(RBNode<T> node){
        //节点为空 默认颜色为黑色 若不为空,获取到当前节点的颜色
        return node== null?Color.BLACK:node.getColor();
    }
    /**
     * 设置节点的颜色
     */
    private void setColor (RBNode<T> node,Color color ){
        node.setColor(color);
    }
    /**
     * 获取当前节点的父亲节点
     */
    private RBNode<T> parent(RBNode<T> node){
        return node.getParent();
    }
    /**
     * 获取当前节点的左孩子节点
     */
    private RBNode<T> left(RBNode<T> node){
        return node.getLeft();
    }
    /**
     * 获取node节点的右孩子节点
     */
    private RBNode<T> right(RBNode<T> node){
        return node.getRight();
    }

2.红黑树的左旋:

左旋原理图解如下:
我们对下图红黑树中的节点40进行左旋操作:
在这里插入图片描述
得到的左旋结果图如下:
在这里插入图片描述
代码实现如下:

/**
     * 红黑树的左旋
     */
    private void leftRotate(RBNode<T> node){
        RBNode<T> child = node.getRight(); //当前节点的孩子节点
        child.setParent(node.getParent());
        if (node.getParent() == null){
            this.root = child;
        }
        else if(node.getParent().getLeft()==node){
            node.getParent().setLeft(child);
        }
        else {
            node.getParent().setRight(child);
        }
        node.setParent(child);
        node.setRight(child.getLeft());
        if (child.getLeft()!=null){
            child.getLeft().setParent(node);
        }
        child.setLeft(node);
        node.setParent(child);
    }

3.红黑树的右旋:

右旋原理图解如下:
我们对下图红黑树中的节点120进行右旋操作:
在这里插入图片描述
得到的右旋结果图如下:
在这里插入图片描述
代码实现如下:

/**
     * 红黑树的右旋操作
     */
    private void rightRotate(RBNode<T> node){
        RBNode<T> child = node.getLeft();
        child.setParent(node.getParent());
        if (node.getParent() == null){
            this.root = child;
        }else if(node.getParent()==null){
            this.root = child;
        }else {
            node.getParent().setRight(child);
        }
        node.setParent(child);
        node.setLeft(child.getRight());
        if (child.getRight()!=null){
            child.getRight().setParent(node);
        }
        child.setRight(node);
    }

4.红黑树的插入操作:

红黑树的根节点是黑色,其他新插入的节点为红色,因为红色节点不影响红黑树的性质,不会对性质5造成影响,如果父节点为黑色,则不需要再进行后续的修复操作,如果父节点为红色,则需要进行后序的修复操作,
需要进行修复操作的情况有以下三种(新插入节点为红色):

1.叔叔节点为红色
2.叔叔节点为黑色,且祖父节点,父节点,和新节点处于一条斜线上
3.叔叔节点为黑色,且祖父节点,父节点,和新节点不处于一条斜线上

在这里插入图片描述
如上图,新插入节点和其父亲节点都为红色,违反了红黑树的规则4,且X的叔叔节点 y 为红色,符合情况1。
**调整方式:**改变某些节点的颜色(把其父节点和叔叔节点设为黑色,祖父节点设为红色)改变结果如下图:
在这里插入图片描述
如上图,x和其父节点都为红色,违反了规则3,叔叔节点y为黑色,且当前节点和其父节点,祖父节点都在同一条斜线上,符合情况3
**调整方式:**对部分节点进行旋转操作(需要进行两次旋转),第一次旋转操作结果如下:
在这里插入图片描述
如上图,当前节点x和其父节点都为红色,(此处指的是更新后的节点)违反了红黑树规则3,叔叔节点为黑色,且当前接节点、父节点、祖父节点位于同一斜线上,符合情况2
**调整方式:**改变某些节点的颜色,对某些节点进行旋转,第二次旋转操作结果如下:
在这里插入图片描述
调整完成

/**
     * 红黑树的插入操作
     */
    private void insert(T data){
        if (root == null){
            //根节点为空时,新插入的节点即为红黑树的根节点,根节点颜色为黑
            root=new RBNode<T>(data,Color.BLACK);
            return;
        }

        RBNode<T> parent = null;//指向父节点的索引
        RBNode<T> cur = root; //指向根节点的索引
        while(cur!=null){ //根节点不为空时
            if (cur.getData().compareTo(data)>0){
                parent = cur;
                cur = cur.getLeft();
            }else if (cur.getData().compareTo(data)<0){
                parent = cur;
                cur = cur.getRight();
            } else {
                return;
            }
        }
        RBNode<T> node = new RBNode<T>(data,null,null,parent,Color.RED);
        if (parent.getData().compareTo(node.getData())>0){
            parent.setLeft(node);
        }else {
            parent.setRight(node);
        }
        //如果新插入的节点父节点是红色,则需要进行红黑树的调整
        if (color(parent) == Color.RED){
            fixAfterInsert(node);
        }
    }

    /**
     * 红黑树的插入调整
     * @param node
     */
    private void fixAfterInsert(RBNode<T> node) {
        //node的父节点是红色 出现连续的红色节点 需要进行颜色调整
        while(color(parent(node)) == Color.RED){
            //当插入的节点在祖父节点的左子树上 (先左旋-再右旋)
            if (left(parent(parent(node))) == parent(node)){
                RBNode<T> uncle = right(parent(parent(node)));
                //1.叔叔节点是红色
                if (color(uncle)==Color.RED){
                    setColor(parent(node),Color.BLACK);//将父节点设置为黑色
                    setColor(uncle,Color.BLACK);//将叔叔节点设置为黑色
                    setColor(parent(parent(node)),Color.RED);//祖父节点设置为红色
                    node = parent(parent(node));//node/指向祖父节点 继续向上回溯调整
                }
                else {
                    //2.叔叔节点是黑色节点,当前节点、父亲节点、祖父节点不在同一直线上
                    if (right(parent(node)) == node){
                        //做一个左旋操作
                        node = parent(node);
                        leftRotate(node);
                    }
                    //3.叔叔节点是黑色,当前节点、父亲节点、祖父节点在一条斜线上
                    //直接进行右旋操作
                    setColor(parent(node),Color.BLACK);//将父节点设置为黑色,旋转以后就成为根节点
                    setColor(parent(parent(node)),Color.RED);//将祖父节点设置为红色
                    //以祖父节点为根节点做一个右旋操作
                    rightRotate(parent(parent(node)));
                    break;//调整完成 跳出调整操作
                }
            }else {
                //插入节点在祖父节点的右子树上,此时和在左子树上的情况刚好相反(先右旋-再左旋)
                RBNode<T> uncle = left(parent(parent(node)));
                //1.叔叔节点是红色
                if (color(uncle)==Color.RED){
                    setColor(parent(node),Color.BLACK);//将父节点设置为黑色
                    setColor(uncle,Color.BLACK);//将叔叔节点设置为黑色
                    setColor(parent(parent(node)),Color.RED);//祖父节点设置为红色
                    node = parent(parent(node));//node/指向祖父节点 继续向上回溯调整
                }
                else {
                    //2.叔叔节点是黑色节点,当前节点、父亲节点、祖父节点不在同一直线上
                    if (left(parent(node)) == node){
                        //做一个左旋操作
                        node = parent(node);
                        rightRotate(node);
                    }
                    //3.叔叔节点是黑色,当前节点、父亲节点、祖父节点在一条斜线上
                    //直接进行右旋操作
                    setColor(parent(node),Color.BLACK);//将父节点设置为黑色,旋转以后就成为根节点
                    setColor(parent(parent(node)),Color.RED);//将祖父节点设置为红色
                    //以祖父节点为根节点做一个右旋操作
                    leftRotate(parent(parent(node)));
                    break;//调整完成 跳出调整操作
                }

            }
        }
        //有可能回溯时把根节点设置为红色 直接把各个节点设置为黑色
        setColor(this.root,Color.BLACK);
    }

4.红黑树的删除操作:

删除时,如果删除的是红色节点,直接删除,如果删除的是黑色节点,删除节点后,违反了规则5,需要对红黑树进行调整。
**操作的总体思想是:**从兄弟节点借调节点,保持局部平衡,如果局部平衡达到了,就看整体的树是否平衡,如果不平衡,就接着向上回溯。
删除修操作分为四种情况(删除黑节点后):
1.待删除节点的兄弟节点是红色,无法借调一个黑色节点过来,但是兄弟节点的孩子节点一定都为黑色,我们可以通过旋转,使兄弟节点的孩子节点成为新的兄弟节点,此时兄弟节点为黑色,可以借调
图解如下:
在这里插入图片描述
2.兄弟节点是黑色节点,且兄弟节点的孩子节点都是黑色的,直接将兄弟节点设置为红色,然后从父节点开始进行回溯调整
图解如下;
在这里插入图片描述
3.兄弟节点是黑色节点,且兄弟节点的左子节点是红色,右子节点是黑色(兄弟节点在右边),如果兄弟节点在左边,则左子节点为黑色,右子节点为红色
图解如下:
在这里插入图片描述
4.待调整节点的兄弟节点是黑色的节点,且右子节点是红色的(兄弟节点在右边),如果兄弟节点在左边,则对应的就是左节点是红色的。
图解如下:
在这里插入图片描述

 /**
     * 红黑树的删除操作
     */
    public void remove(T data){
        if (this.root == null){
            return;
        }
        RBNode<T> cur = this.root;
        while (cur != null){
            if (cur.getData().compareTo(data)>0){
                cur= cur.getLeft();
            } else if (cur.getData().compareTo(data)<0){
                cur = cur.getRight();
            }else {
                break;
            }
        }
        if (cur==null){
            return;
        }
        //#待删除节点有两个孩子,转化为有一个孩子或者叶子节点的删除
        if (cur.getLeft() != null&&cur.getRight()!=null){
            //用后继节点代替 后继节点:待删除节点右子树中,值最小的节点
            RBNode<T> old = cur;
            cur = cur.getRight();
            while(cur.getRight() != null){
                cur=cur.getLeft();
            }
            old.setData(cur.getData());
        }
        //# 待删除节点有一个孩子或者没有孩子
        RBNode<T> child = (cur.getLeft()!=null?cur.getLeft():cur.getRight());
        if (child != null){
            child.setParent(cur.getParent());
            if (cur.getParent()==null){
                this.root = child;
            } else if (cur.getParent().getLeft() == cur){
                cur.getParent().setLeft(child);
            } else {
               cur.getParent().setRight(child);
            }
            //如果删除的是黑色节点 需要进行调整
            if (cur.getColor()==Color.BLACK){
                fixAfterInsert(child);
            }
        }
        else {
            if (cur.getParent() == null){
                this.root=null;
            } else {
                //删除的是叶子节点
                if (cur.getColor() == Color.BLACK){
                    fixAfterInsert(cur);
                }
                //当前删除节点cur 没有孩子节点 上面把cur当作虚拟节点,进行红黑树删除调整
                //完成后 把cue节点删掉
            }
            if (cur.getParent().getLeft() == cur){
                cur.getParent().setLeft(null);
            }else {
                cur.getParent().setRight(null);
            }
        }
    }

/***
 * 红黑树的删除调整
 */
private void fixAfterDelete(RBNode<T> node){
    //回溯过程中遇到根节点或者红色节点结束
    while (node!=this.root && color(node)==Color.BLACK){
        if (node == left(parent(node))){
            RBNode<T> b = right(parent(node));
            //1.兄弟节点是红色 无法借调一个节点,进行旋转操作,
            // 将兄弟节点的黑色孩子节点向上提,使其成为可以借调的兄弟节点
            if (color(b) == Color.RED){
                b.setColor(Color.BLACK); //将兄弟节点颜色设为黑色
                parent(node).setColor(Color.RED);//将父节点颜色设为红色
                leftRotate(parent(node));//对其进行左旋
                b=right(parent(node));
            }
            //2.兄弟节点是黑色,而且兄弟节点的两个孩子节点也是黑色节点,把兄弟黑带你改为红色,继续回溯父节点
            if (color(left(b)) == Color.BLACK){
                if (color(right(b)) == Color.BLACK){
                    left(b).setColor(Color.BLACK);
                    b.setColor(Color.RED);
                    rightRotate(b);
                    b = right(parent(node));
                } else {
                    //3.兄弟节点是黑色,但是右孩子没有红色
                    if (color(right(b)) == Color.BLACK){
                        left(b).setColor(Color.BLACK);
                        b.setColor(Color.RED);
                        rightRotate(b);
                        b = right(parent(node));
                    }
                    //4.兄弟节点是黑色,且其右孩子是红色 直接左旋,并将右孩子直接调整成黑色
                    b.setColor(parent(node).getColor());
                    parent(node).setColor(Color.BLACK);
                    right(b).setColor(Color.BLACK);
                    leftRotate(parent(node));
                    node = this.root;

                }
            }
        }
        /**
         * #删除黑色节点后,其孩子节点是红色,直接调成 黑色,结束上面的循环,保持黑色节点的数量保持不变
         * #删除节点后,其孩子节点是黑色,向上回溯的过程中,遇到红色节点直接改为黑色节点,
         * 结束上面的循环,保持黑色节点的数量不变
         */
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值