【数据结构笔记二】AVL-平衡树__2-3-4树__红黑树实现(b站波哥)

4,AVL-平衡树

​ BST存在的问题是, 树在插入的时候会导致倾斜, 不同的插入顺序会导致数的高度不一样, 而树的高度直接影响了树的查找效率。

​ 最坏的情况所有的节点都在一条斜线上,这样树的高度为N。

​ 基于BST存在的问题, 平衡查找二叉树(Balanced BST)产生了。

​ 平衡树的插入和删除的时候, 会通过旋转操作将高度保持在LogN。

​ 其中两款具有代表性的平衡术分别为AVL树(高度平衡树, 具备二叉搜索树的全部特性, 而目左右子树高度差不超过1 )和红黑树。

AVL树是如何实现平衡的呢?,具体是通过左旋或者右旋来实现的。具体如下图:

image-20220320210603731

填加8 9 10 时 左右高度超过1 右旋

image-20220320211054246

添加6 , 7 时 第二次根据8 右旋

image-20220320211340625

添加 5 第三次根据9 右旋

image-20220320211547033

添加4 第四次根据6旋转

image-20220320211718750

添加 2 ,3 ,结果 4不平衡了, 第五次根据4旋转

image-20220320211941002

添加1 之后, 5不平衡了, 第六次根据5旋转

image-20220320212436975

平衡树转成红黑树

image-20220320212625178

  • 红黑数要求没有平衡数那么严格, 只要保证黑节点平衡: 每条道黑节点的数量相同

5, 2-3-4 树

5.1 概念介绍

2-3-4树是四阶的 B树(Balance Tree), 他属于一种多路查找树, 它的结构有以下限制:

所有叶子节点都拥有相同的深度。

节点只能是 2-节点、3-节点、4-节点之一。

  • 2-节点:包含1个元素的节点,有2个子节点:
  • 3-节点:包含2个元素的节点,有3个子节点:
  • 4-节点:包含3个元素的节点,有4个子节点:

所有节点必须至少包含1个元素

元素始终保持排序顺序,整体上保持二叉查找树的性质,即父结点大于左子结点,小于右子结点:

而且结点有多个元素时,每个元素必须大于它左边的和它的左子树中元素。

下图是一个典型的2-3-4树

image-20220320213320896

  • 2节点 : 5
  • 3节点 : 7 9
  • 4节点 : 10 11 12

​ 2-3-4树的查间操作像普通的二叉搜索树一样,非常简单,但由于其结点元素数不确定,在一些编程语言中实现起来并不方便,实现一般使用它的等同------------红黑树。

5.2 添加情况

image-20220320214153507

出现5节点时会发生裂变

image-20220320214902968

image-20220320215002719

发现: 都是从底层插入的, 超过4节点后裂变

5.3 , 和红黑树的等价关系

2节点

image-20220320222257196

3节点

image-20220320222337518

4节点

image-20220320222409293

超过4节点裂变的情况

image-20220320222616711

转换成红黑树

image-20220320223043180

image-20220320223237818

  • 看出根节点到每条路径的黑节点都是3

6,红黑树

红黑树, Red-Black Tree [RBT]是一个自平衡(不是绝对的平衡)的二叉查找树(BST), 树上的每个节点都遵循下面的规则:

  1. 每个节点要么是黑色,要么是红色。
  2. 根节点是黑色。
  3. 每个叶子节点 (NIL) 是黑色。
  4. 每个红色结点的两个子结点一定都是黑色。(不存在两个相邻的红色节点)
  5. 任意一结点到每个叶子结点的路径都包含数量相同的黑结点。

红黑树能自平衡, 它靠的是什么? 三种操作: 左旋、右旋和变色

操作描述
左旋以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点
右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变
右旋以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,
左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变。
变色结点的颜色由红变黑或由黑变红。

定义红黑树类

public class RBTree<K extends Comparable<K>, V> {
    //红色用false来表示
    private static final boolean RED = false;
    private static final boolean BLACK = true;

    //红黑树的root节点
    private RBNode root;

    public RBNode getRoot() {
        return root;
    }

    public void setRoot(RBNode root) {
        this.root = root;
    }

	//红黑树对象内部类
    //继承Comparable接口, 可以做比较
    static class RBNode<K extends Comparable<K>,V>{
        //父节点
        private RBNode parent;
        //左子节点
        private RBNode left;
        //右子节点
        private RBNode right;
        //颜色,  这里定义的是 黑--true  红--false
        private boolean color;

        private  K key;

        private  V value;

        public RBNode() {

        }

        public RBNode(RBNode parent, RBNode left, RBNode right, boolean color, K key, V value) {
            this.parent = parent;
            this.left = left;
            this.right = right;
            this.color = color;
            this.key = key;
            this.value = value;
        }
        //为了操作简化, 再添加一个parent,key,value
        public RBNode(RBNode parent, K key, V value) {
            this.parent = parent;
            this.key = key;
            this.value = value;
        }

        public RBNode getParent() {
            return parent;
        }

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

        public RBNode getLeft() {
            return left;
        }

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

        public RBNode getRight() {
            return right;
        }

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

        public boolean isColor() {
            return color;
        }

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

        public K getKey() {
            return key;
        }

        public void setKey(K key) {
            this.key = key;
        }

        public V getValue() {
            return value;
        }

        public void setValue(V value) {
            this.value = value;
        }
    }
}

6.1, 旋转操作

左旋

以某个节点作为旋转点,其右子节点变为旋转节点的父节点,右子节点的左子节点变为旋转节点的右子节点,左子节点保持不变。

image-20220320224258799

右旋

以某!个节点作为旋转点,其左子节点变为旋转节点的父节点,左子节点的右子节点变为旋转节点的左子节点,右子节点保持不变。

image-20220320224404213

红黑树-旋转代码实现
1,左旋代码实现

左旋

在这里插入图片描述

只改变了两根线的指向,

  • rl父节点指向p, p, 右子节点指向rl
  • p的父节点指向pr, pr左子节点指向p
  /**
     * 实现左旋
     *      p                          pr
     *      /\                        /\
     *     pl  pr    ==>       p      rr
     *          /\              /\
     *         rl  rr         pl   rl
     *  左旋操作:
     *  不用变的:  p-pl , pr-rr
     *  需要调整:  1. pr-rl 调整为  p-rl ,
     *                    将rl调整为p的右子节点
     *                    将p调整为rl的父节点
     *                2.判断p是否有父节点
     *                有:  把pr.parent = p.parent
     *                     p为p.parent的子节点, 到底是 左还是右呢
     *                       if p.parent.left == p
     *                           p.parent.left = pr
     *                       else
     *                           p.parent.right = pr
     *                没有: 直接把pr设置成root节点
     *                3.最后  把p 和 pr交换
     *                p.parent = pr;  pr.left = p
     *
     * @param p, 需要旋转的节点
     */
    //左旋方法
    private  void  leftRotate(RBNode p){
        if ( p != null ){
            //获取到pr节点
            RBNode pr = p.right;
            //获取到pr的左子节点 rl
            p.right =pr.left;
//            pr = rl;
            if ( pr.left != null ) {
                //将rl调整为p的右子节点
//                p.right = rl;
                //将p调整为rl的父节点
                pr.left.parent = p;
            }
            //2,判断p是否有父节点
            pr.parent = p.parent; //不管p是否有父节点, 都设置为pr的父节点
            if ( p.parent == null ){
                //如果p.parent 为空, 说明p为root节点, 把root改为pr
                root = pr ;
                //如果p是p.parent的左子节点, 就变更为p.parent.left  = pr
            }else  if ( p.parent.left == p ){
                p.parent.left = pr;
            }else {//如果p是p.parent的右子节点, 就变更为p.parent.right  = pr
                p.parent.right = pr;
            }
            //3 最后 把p父节点设置为pr, pr的左子节点设置为p
            pr.left = p;
            p.parent = pr;
        }
    }

2,右旋代码实现

右旋

    /**
     * 右旋实现
     *      p                 pl
     *      /\                /\
     *    pl  pr    ==>    ll    p
     *    /\                    /\
     *   ll lr               lr   pr
     * 不变的 p-pr , pl-lr,  两个2节点的右字节点不变
     * 步骤1: p-pl 变成 p-lr
     * 步骤2: 判断p是否有父节点 , pl-lr 变成 pr-p
     * 步骤3:  p-pl 变成 pl-p
     * @param p
     */
    //右旋方法
    private  void  rightRotate(RBNode p){
        if (p != null) {
            RBNode pl = p.left;
            p.left = pl.right;
            //步骤1: p-pl 变成 p-lr , lr的父节点 = p
            if (pl.right != null) {
//                p.left = lr;
                pl.right.parent = p;
            }
            //步骤2: 判断p是否有父节点,
            pl.parent = p.parent ;
            if (p.parent == null) {
                root = pl;
            }else if ( p.parent.left == p ){
                p.parent.left = pl;
            }else {
                p.parent.right = pl;
            }
            //步骤3:  p-pl 变成 pl-p
            pl.right = p;
            p.parent = pl;
        }
    }

6.2, 插入操作

image-20220321132426970

1, 先完成插入节点
  • a.找到插入的位置(父节点)
  • b.将节点插入父节点相应的位置
//插入操作
private void put(K key,V value){
    //1, 找到插入的父节点
    RBNode t = root;
    if ( t == null ){ //这种情况说明是第一次插入
        root = new RBNode(null,key,value==null?key:value);
        return;
    }
    //记录比较情况
    int cmp;
    RBNode parent;
    do {
        parent = t;
        //比较当前根节点与传入的key大小来决定从哪边开始查找
        cmp = key.compareTo((K) t.key);
        if ( cmp > 0){ //从右侧查找
            t = t.right ;
        }else if ( cmp < 0 ){//从左侧查找
            t = t.left ;
        }else {// cmp =0 说明key相等了
            //如果value为空就设置为key
            t.setValue( value == null ? key:value);
            return;
        }
    }while (t != null);
    //2, 将新节点添加到父节点的子节点中
    // a 创建要插入的节点
    RBNode node = new RBNode(parent, key, value == null ? key : value);
    if ( cmp > 0 ){
        //如果比父节点大, 就放到右边
        parent.right = node;
    }else {
        parent.left = node;
    }
    //旋转和变色 调整红黑树的平衡
}
2,接下来需要旋转和变色 调整红黑树的平衡
2-3-4数插入节点情况
2节点

如果是二节点插入1个节点, 只需判断放左右, 黑红数是平衡的

image-20220321140405385

3节点

如果是3节点插入的话, 会出现6种情况

只有1, 6 不需要调整 , 3 ,4 旋转一次 变色就可以了

image-20220321142100183

需要旋转两次的

图2先右旋再左旋

右旋再左旋

图5先左旋再右旋

左旋再右旋

4节点

会出现4种情况, 每种情况都需要调整

image-20220321142731644

3.代码实现插入操作
需要调整的共8种情况

image-20220321185520924

代码里面的例图

有右叔叔节点情况, 需变色(右叔叔跟父亲变为黑色, 爷爷变成红色), 这时平衡了

但是如果爷爷节点上面还有元素, 这时候就不平衡了;

这时的爷爷节点也是红色,就把爷爷节点当成新插入的元素再和上面再来一次变色处理(递归)

image-20220321192617027

x = getParent(x)做完这步之后 x = 2了

现在发现, 图6 旋转一次后 就和 图5 一模一样了 , 可以一起处理了

image-20220321200248285

4 put方法
package com.ccc.util.treemap;

public class RBTree<K extends Comparable<K>, V> {
    //红色用false来表示
    private static final boolean RED = false;
    private static final boolean BLACK = true;

    //红黑树的root节点
    private RBNode root;

    public RBNode getRoot() {
        return root;
    }

    public void setRoot(RBNode root) {
        this.root = root;
    }
    //插入操作
    public void put(K key,V value){
        //1, 找到插入的父节点
        RBNode t = root;
        if ( t == null ){ //这种情况说明是第一次插入
            root = new RBNode(null,key,value==null?key:value);
            return;
        }
        //记录比较情况
        int cmp;
        RBNode parent;
        if (key == null){
            System.out.println("put 命令里 key 为空了");
            throw new NullPointerException();
        }
        do {
            parent = t;
            //比较当前根节点与传入的key大小来决定从哪边开始查找
            cmp = key.compareTo((K) t.key);
            if ( cmp > 0){ //从右侧查找
                t = t.right ;
            }else if ( cmp < 0 ){//从左侧查找
                t = t.left ;
            }else {// cmp =0 说明key相等了
                //如果value为空就设置为key
                t.setValue( value == null ? key:value);
                return;
            }
        }while (t != null);
        //2, 将新节点添加到父节点的子节点中
        // a 创建要插入的节点
        RBNode node = new RBNode(parent, key, value == null ? key : value);
        if ( cmp > 0 ){
            //如果比父节点大, 就放到右边
            parent.right = node;
        }else {
            parent.left = node;
        }
        //旋转和变色 调整红黑树的平衡
        fixAfterPut(node);
    }
    //获取父节点
    private RBNode getParent(RBNode node){
        return node != null ? node.parent : null;
    }
    //获取爷节点
    private RBNode getGrandfather(RBNode node){
        return node != null ? node.parent.parent : null;
    }
    //获取左节点
    private RBNode getLeft(RBNode node){
        return node != null ? node.left : null;
    }
    //获取右节点
    private RBNode getRight(RBNode node){
        return node != null ? node.right : null;
    }
    //获取节点颜色
    private boolean getColor(RBNode node){
        //如果传过来的节点是空的, 就返回黑色, ----------------Treemap里删除的时候会用到这个表达式
        return node == null ? BLACK : node.color;
    }
    //设置颜色
    private void setColor(RBNode node, boolean color){
        if (node != null) {
            node.color = color;
        }
    }
    /**
     * 插入放点后的调整操作
     * 2-3-4对应的操作
     *     2书点: 新插入一个元素直接2节点合并不用调整
     *    	 	红黑树:新增一个红色 放点在黑色节点下不需要调整
     *     3节点: 新插入一个元素在3节点下, 那么会出现6中情况(2两种不需要调整,4种需要调整)
     *     	红黑树: 插入的节点是 插入在上黑下红的结构中,插入在红色节点
     *     4节点:新插入一个元素在4节点下,那么会出现4中情况都需要调整
     *     	红黑树:新增的节点是红色, 爷爷节点是黑色,父亲节点和叔叔节点是红色
     * @param x
     */
    private void  fixAfterPut(RBNode<K,Object> x){
        //插入的节点  肯定是红色
        x.color = RED;
        //2节点不用调整, 3, 4 节点才需要调整, 把2节点过滤了
        while ( x !=null && x != root && x.parent.color ==RED ){ //为空为root都不需要调整, 只有2节点的父节点是黑色,其他都是红色, 所以这里把2节点排除了
            //这里分为了两种情况,
                if ( getParent(x) == getLeft(getGrandfather(x))  ){  //x的父节点在x爷爷左子节点时  对应 图1 , 2 ,5, 6
                    //需调整的变成了4种, 有叔叔节点: 图1,2     没有叔叔节点: 图5,6
                    //找到右叔叔节点, 1,2找到4 , 5,6 找到null
                    RBNode uncleR = getRight(getGrandfather(x));
                    //如果右叔叔的颜色是RED, 说明不为空 : 图 1,2
                    if (getColor(uncleR) == RED) {
                        //叔叔跟父亲变为黑色, 爷爷变成红色
                        //但是还有一种情况是爷爷节点上面还有元素, 这下就把(下面这个整体)爷爷节点当成x ,再来一遍 如图9
                        //变色+ 递归
                        setColor(uncleR,BLACK); //把右叔叔设置成黑色
                        setColor( getParent(x) , BLACK ); //把父节点设置成黑色
                        setColor(getGrandfather(x),RED);//把爷爷设置成红色
                        //递归处理,  就是先保证自身平衡, 再把这部分当做新元素和上面的节点变色处理
                        x = getGrandfather(x);

                    }else {
                        //说明没有右叔叔节点: 图 5,6
                        //判断x是父节点的左子节点还是右子节点
                        if ( x == getRight(getParent(x))) { //是父节点的右子节点: 图6
                            x = getParent(x);//现在x就转移到了2 , 如图10
                            leftRotate(x); // 旋转之后, 下面5, 和10 可以一起处理了
                        }
                        //把父节点变为黑色, 爷爷节点变为红色, 再根据爷爷节点右旋即可
                        setColor(getParent(x),BLACK);//父黑
                        setColor(getGrandfather(x),RED);//爷红
                        rightRotate(getGrandfather(x));//爷右旋
                    }
                }else {//x的父节点在x爷爷右子节点时  对应 图3,4,7,8
                    //需调整的变成了4种, 有叔叔节点: 图3,4     没有叔叔节点: 图7,8 , 和上面的情况左右相反
                    //找到左叔叔节点, 图3,4找到2 , 7,8 找到null
                    RBNode uncleL = getLeft(getGrandfather(x));
                    //如果左叔叔的颜色是RED, 说明不为空 : 图 3,4
                    if (getColor(uncleL) == RED) {
                        //叔叔跟父亲变为黑色, 爷爷变成红色
                        //但是还有一种情况是爷爷节点上面还有元素, 这下就把(下面这个整体)爷爷节点当成x ,再来一遍 如图9
                        //变色+ 递归
                        setColor(uncleL,BLACK); //把左叔叔设置成黑色
                        setColor( getParent(x) , BLACK ); //把父节点设置成黑色
                        setColor(getGrandfather(x),RED);//把爷爷设置成红色
                        //递归处理,  就是先保证自身平衡, 再把这部分当做新元素和上面的节点变色处理
                        x = getGrandfather(x);

                    }else {
                        //说明没有左叔叔节点: 图 7,8
                        //判断x是父节点的左子节点还是右子节点
                        if ( x == getLeft(getParent(x))) { //是父节点的右子节点: 图7
                            x = getParent(x);//现在x就转移到了3
                            rightRotate(x); // 旋转之后, 下面7, 和8 可以一起处理了
                        }
                        //把父节点变为黑色, 爷爷节点变为红色, 再根据爷爷节点右旋即可
                        setColor(getParent(x),BLACK);//父黑
                        setColor(getGrandfather(x),RED);//爷红
                        leftRotate(getGrandfather(x));//爷右旋
                    }
                }
        }
        // root 节点肯定为黑色
        setColor(root , BLACK);
    }

    
}

5测试put方法

请添加图片描述

运行结果

image-20220321221819016

网站验证

Red/Black Tree Visualization (usfca.edu)

image-20220321221911413

6.3, 删除操作

image-20220321223957377

6.3.1 二叉树删除

二叉树删除操作的情况:

  1. 删除叶子节点,直接删除
  2. 删除的节点有一个子节点,那么用子节点来替代
  3. 如果删除的节点右两个子节点,此时需要找到前驱节点或者后继节点来替代,可以转换为 1、2的情况

删除节点方案:

  1. 找到前驱节点,复制前驱节点值覆盖预备删除的节点的值,然后删除前驱节点
  2. 找到后继节点,复制后继节点值覆盖预备删除的节点的值,然后删除后继节点

被删除的前驱或者后继节点只有两种情况

  1. 被删除的节点是叶子节点
  2. 被删除的节点有一个孩子节点
6.3.2查找前驱后继节点
    /**
     * 找到前驱节点
     * @param node
     * @return
     */
    private RBNode getPrecursor(RBNode node){
        if (node != null  ) {
            return null;
        } else if (getLeft(node) != null) {
            //如果他的左节点不为空, 就循环找他的左节点的右子节点
            RBNode p = getLeft(node);
            while ( getRight(p) != null){
                p = getRight(p);
            }
            return  p;
        }else {
            //如果这个node没有左节点, 那么就向上找前驱节点
            //这种情况在 红黑树 ,2-3-4树中不会出现
            RBNode p = node.parent;
            RBNode n1 = node;
            while ( p!= null && n1 == getLeft(p)){
                n1 = p;
                p = getParent(p);
            }
            return p;
        }
    }
    /**
     * 找到后继节点
     * @param node
     * @return
     */
    private RBNode getSuccessor(RBNode node){
        if (node != null  ) {
            return null;
        } else if (getRight(node) != null) {
            //如果他的左节点不为空, 就循环找他的左节点的右子节点
            RBNode p = getRight(node);
            while ( getLeft(p) != null){
                p = getLeft(p);
            }
            return  p;
        }else {
            //如果这个node没有左节点, 那么就向上找前驱节点
            //这种情况在 红黑树 ,2-3-4树中不会出现
            RBNode p = node.parent;
            RBNode n1 = node;
            while ( p!= null && n1 == getRight(p)){
                n1 = p;
                p = getParent(p);
            }
            return p;
        }
    }
6.3.3 . 红黑树删除代码实现
  • 删除叶子节点,直接删除
  • 删除的节点有一个子节点,那么用子节点来替代
  • 如果删除的节点右两个子节点,此时需要找到前驱节点或者后继节点来替代,可以转换为 1、2的情况
 /**
     * 删除节点
     *      1, 节点删除(可以看做普通的二叉树删除)
     *      2, 删除后调整
     * @param key
     * @return
     */
    public V remove(K key){
        //1. 根据需要删除的key, 找到对应的node节点
        RBNode node = getNode(key);
        if ( node == null ){
            return  null;
        }
        V oldValue = (V) node.value;
        //具体删除节点的方法
        deleteNode(node);
        return oldValue;
    }


    /**
     * 根据key找到删除的对应node
     * @param key
     * @return
     */
    private RBNode getNode(K key) {
        if ( key == null ){
            return null;
        }
        RBNode node = root;
        while ( node != null ){
            int cmp = node.key.compareTo(key);
            if (cmp > 0) {
               node = node.right ;
            } else if (cmp < 0) {
                node = node.left;
            }else {
                //找到了对应的节点
                return node;
            }
        }
        return null;
    }

    /**
     * 删除节点
     * 1.删除节点(普通的二叉树相同)
     *      a. 删除叶子节点, 直接删除
     *      b. 删除的节点有一个子节点, 用子节点来替代
     *      c. 删除的节点有两个子节点, 把这个节点赋值给前驱或后继节点, 再删除掉前去后继节点
     *      将情况c转换成情况a, b
     * 2.调整
     * @param node
     */
    private void deleteNode(RBNode node) {
        //1, 先处理情况3
        if (getLeft(node) != null && getRight(node) != null) {
            //有两个子节点的情况
            RBNode pNode = getSuccessor(node);//找到后继或前驱节点,这里使用后继
            //用后继节点的值, 覆盖给删除节点
            node.key = pNode.key;
            node.value = pNode.value;
            //这时要删除的节点就变成了 后继节点, 也就变成了情况2 ,或1了
            node = pNode;
        }
        //找到后继节点的子节点(替代节点)
        RBNode replacement = node.left==null ? node.right:node.left;
        //2, 再情况2, 情况2 可能是情况3 转换来的
        if (replacement != null) { //如果后继节点有子节点说明是情况2
            replacement.parent = node.parent;
            if ( getParent(node) == null ){//说明我们删除的节点是根节点
                root = replacement;
            } else if (getLeft(getParent(node)) == node) { //如果是node父节点的左节点, 就换成replacement
                getParent(node).left = replacement;
            }else { //如果是node父节点的右节点, 就换成replacement
                getParent(node).right = replacement;
            }
            //要删除的node节点全部设置成空 , GC
            node.right = node.parent = node.right = null ;
            if (getColor(node) == BLACK) { //如果删除的节点是红色, 不需要调整
                //做调整操作
                fixAfterRemove(replacement);
            }
        }else if (node.parent == null){ // node 没有父节点说明是root节点
                root = null;
        }else { //就是情况1
            //先调整再删除
            if (node.color == BLACK) {
                fixAfterRemove(node);
            }
            if (node.parent != null) { //如果他没有父节点再把指向关系去掉
                getParent(node).left = getParent(node).right  = null ;
            }
            node = null;
        }
    }

    private void fixAfterRemove(RBNode node) {

    }
6.3.4 删除后的调整

在整理红黑树节点的删除操作时我们需要先理解清楚红黑树删除和2-3-4树删除的等价关系,这样理解起 来才会比较容易

核心理论:红黑树删除操作的本质其实就是删除2-3-4树的叶子节点

img

情况一

img

情况2:删除的是非情况1的节点,根据我们前面介绍的删除的规则,会找到对应的前驱和后继节点,那 么最终删除的还是叶子节点

img

删除节点的调整操作:

1.情况一:自己能搞定的,对应叶子节点是3节点和4节点

img

2.情况二:自己搞不定,需要兄弟借,但是兄弟不借,找父亲借,父亲下来,然后兄弟找一个人去代替

父亲当家

这种情况就是兄弟节点是3节点或者4节点 找兄弟节点

img

如果找到的兄弟节点是红色其实还要调整

在这里插入图片描述

执行如下调整先,先变色,然后左旋

img

img

找兄弟节点借

请添加图片描述

然后沿着7节点左旋
请添加图片描述

3.情况三:跟兄弟借,兄弟也没有(情同手足,同时自损)

兄弟节点是2节点,同时当前节点的父节点是红色节点的情况

img

删除后直接变色就可以了 兄弟节点是2节点,同时当前节点的父节点是黑色节点

img

变更操作为如下,如果继续有父节点那么还要递归处理

img

最终的是实现代码为

    /**
     * 删除节点后的调整操作
     * 2-3-4数操作
     *  1. 删除3,4 节点, 自己能搞定
     *  2. 删除2 节点, 自己搞不定, 需要兄弟借, 兄弟借
     *      父亲下来, 兄弟找一个节点替换父亲节点位置
     *  3. 删除2 节点, 自己搞不定, 兄弟不借
     *
     * @param node
     */
    private void fixAfterRemove(RBNode node) {
        // 情况2 和3
        //如果是root节点直接改为黑色就可以了, 替换的节点是红色就不需要调整了
        while (node != root && getColor(node) == BLACK ){
            //判断node是父节点的左节点还是有节点
            if ( node == getLeft(getParent(node))){
                //1. 找到兄弟节点
                RBNode bro = getParent(node).right;
                // 判断找到的是不是真的兄弟节点 ---- 2-3-4 转红黑树3节点有两种情况
                if (getColor(bro) == RED) {
                    //如果是红色, 找到的就不是真的兄弟节点, 需要一次变色加左旋转
                    setColor(bro,BLACK);
                    setColor(getParent(node),RED);
                    leftRotate(getParent(node));
                    bro = getParent(node).right;//找到真兄弟了
                }
                //判断能不能借
                //兄弟节点一个子节点都没有, 不借
                if (getColor(getLeft(bro)) == BLACK && getColor(getRight(bro)) == BLACK) {
                    //2没有子节点, 不借
                    setColor(bro,RED);
                    node = getParent(node);
                }else {
                    //2兄弟借
                    //如果兄弟节点的子节点是左边, 需要先变色, 右旋一次
                    if (getColor(getRight(bro)) == BLACK) {
                        //右侧子节点为空, 那就有左子节点
                        setColor(bro,RED);
                        setColor(getLeft(bro),BLACK);
                        rightRotate(bro);
                        bro = getRight(getParent(node));
                    }
                    //需要根据父节点做一次左旋操作, 变色
                    setColor(bro,getColor(getParent(node)));
                    setColor(getParent(node),BLACK);
                    setColor(getRight(bro),BLACK);
                    leftRotate(getParent(node));
                    node =root; //结束循环, 针对3的处理
                }
            }else {
                //1. 找到兄弟节点
                RBNode bro = getParent(node).left;
                // 判断找到的是不是真的兄弟节点 ---- 2-3-4 转红黑树3节点有两种情况
                if (getColor(bro) == RED) {
                    //如果是红色, 找到的就不是真的兄弟节点, 需要一次变色加左旋转
                    setColor(bro,BLACK);
                    setColor(getParent(node),RED);
                    rightRotate(getParent(node));
                    bro = getParent(node).left;//找到真兄弟了
                }
                //判断能不能借
                //兄弟节点一个子节点都没有, 不借
                if (getColor(getLeft(bro)) == BLACK && getColor(getRight(bro)) == BLACK) {
                    //2没有子节点, 不借
                    setColor(bro,RED);
                    node = getParent(node);
                }else {
                    //2兄弟借
                    //如果兄弟节点的子节点是左边, 需要先变色, 右旋一次
                    if (getColor(getLeft(bro)) == BLACK) {
                        //右侧子节点为空, 那就有左子节点
                        setColor(bro,RED);
                        setColor(getRight(bro),BLACK);
                        leftRotate(bro);
                        bro = getLeft(getParent(node));
                    }
                    //需要根据父节点做一次左旋操作, 变色
                    setColor(bro,getColor(getParent(node)));
                    setColor(getParent(node),BLACK);
                    setColor(getLeft(bro),BLACK);
                    rightRotate(getParent(node));
                    node =root; //结束循环, 针对3的处理
                }
            }
        }



        //1. 替换的节点为红色, 只需要变色为黑色集客
        setColor(node,BLACK);
    }

6.4测试删除结果 (前驱)

初始状态一致

image-20220322144343258

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值