BST树、AVL树和红黑树

一、引入

1.1什么是BST树(二叉搜索树)

  • 若左子树不为空,左子树所有节点值小于根节点。
  • 若右子树不为空,右子树所有节点值大于根节点。
  • 左右子树也是BST树
     BST树
    作用:可以快速查找到需要的值,常用在数据的查找上。
    效率:最好O(logn),数据本来有序的时候O(n)
    缺点:当插入的数据本来的就是有序的时候就退化成了链表。
    例如:
    在这里插入图片描述
    为了解决这个问题出现了二叉平衡树(AVL树)

二、AVL树

  1. 具有BST树的所有特性。
  2. 每个节点的左子树和右子树的高度差最多为1。
    在这里插入图片描述
    图一是AVL树。
    在这里插入图片描述
    图二由于高度差大于1,不是AVL树。

2.1左旋和右旋?

在图一的基础上插入节点3,正常情况下会插入到4的左边,这样又破化了AVL的特性,此时就引入了左旋和右旋。

左-左型树通过右旋恢复平衡
在这里插入图片描述
即:顺时针旋转两个节点,使得父节点被自己的左孩子取代,而自己成为自己的右孩子
在这里插入图片描述
节点4和9高度相差大于1。由于是左孩子的高度较高,此时是左-左型,进行右旋。

右-右型树通过左旋恢复平衡
在这里插入图片描述
3和6的子树高度差大于1
即:逆时针旋转两个节点,使得父节点被自己的右孩子取代,而自己成为自己的左孩子

2.2AVL树的形成?

每次插入节点时按照BST树的规则进行(小的在左,大的在右)并检测每个节点的左右子树高度差,如果高度差大于1就进行左旋/右旋使其形成AVL树,后面继续插入节点时继续判断。
在这里插入图片描述
特殊情况:
在这里插入图片描述
右-左型: 先对10右旋变为右右型,然后再进行左旋。
在这里插入图片描述
在这里插入图片描述
左-右型同理
在这里插入图片描述

2.3总结

1、左-左型:做右旋。
2、右-右型:做左旋转。
3、左-右型:先做左旋,后做右旋。
4、右-左型:先做右旋,再做左旋。

不理解的看看代码思考原理
java代码实现:

class AvlNode{
    int data;
    AvlNode l_child;
    AvlNode r_child;
    // 高度从底往上算
    int height;
}
public class AvlTree {
    /**
     * 返回树的高度
     * @param node
     * @return
     */
    static int height(AvlNode node){
        if (node == null){
            return 0;
        } else {
            return node.height;
        }
    }

    /**
     * 右旋,返回中间节点
     * @param node 中间节点
     */
    static AvlNode R_Rotate(AvlNode node){
        AvlNode k;

        // 旋转
        k = node.l_child;
        node.l_child = k.r_child;
        k.r_child = node;
        // 重新计算节点高度
        node.height = Math.max(height(node.l_child),height(node.r_child)) + 1;
        k.height = Math.max(height(k.l_child),height(k.r_child)) + 1;
        return k;
    }

    /**
     * 左旋
     * @param node
     */
    static AvlNode L_Rotate(AvlNode node){
        AvlNode k;

        k = node.r_child;
        node.r_child = k.l_child;
        k.l_child = node;

        node.height = Math.max(height(node.l_child),height(node.r_child)) + 1;
        k.height = Math.max(height(k.l_child),height(k.r_child)) + 1;
        return k;
    }

    /**
     * 先右旋再左旋(右左型)
     * @param node
     */
    static AvlNode R_L_Rotate(AvlNode node){
        // 先对其孩子进行右旋
        node.r_child = R_Rotate(node.r_child);
        // 再对自己进行左旋
        return L_Rotate(node);
    }

    /**
     * 先左旋再右旋(左右型)
     * @param node
     */
    static AvlNode L_R_Rotate(AvlNode node){
        node.l_child = L_Rotate(node.l_child);

        return R_Rotate(node);
    }

    /**
     * 插入节点(存在节点的情况下)
     * @param node
     * @return 返回根节点
     * 如果节点不存在则创建一个节点并返回,存在则一直递归直至不存在
     */
    static AvlNode insert(int data,AvlNode node){
        if (node == null){
            node = new AvlNode();
            node.data = data;
            node.l_child = node.r_child = null;
        } else if(data < node.data){
            // 向左孩子递归插入
            node.l_child = insert(data,node.l_child);
            // 调整,如果左孩子比右孩子高度大2
            if ((height(node.l_child) - height(node.r_child)) == 2){
                if (data < node.l_child.data){
                    // 左左型,直接右旋
                    node = R_Rotate(node);
                } else {
                    // 左右型
                    node = L_R_Rotate(node);
                }
            }
        } else if (data > node.data){
            // 向右孩子递归插入
            node.r_child = insert(data,node.r_child);
            // 调整
            if ((height(node.r_child) - height(node.l_child)) == 2){
                // 右子树高度比左子树高2
                if (data > node.r_child.data){
                    // 右右型,直接左旋
                    node = L_Rotate(node);
                } else {
                    // 右左型
                    node = R_L_Rotate(node);
                }
            }
        }
        node.height = Math.max(height(node.l_child),height(node.r_child)) + 1;
        return node;
    }

    /**
     * 中序遍历
     * @param node
     */
    static void inorder(AvlNode node){
        if (node == null){
            return;
        }
        inorder(node.l_child);
        System.out.println(node.data);
        inorder(node.r_child);
    }
    public static void main(String[] args) {
        AvlNode root = null;
        root = insert(3,root);
        root = insert(1,root);
        root = insert(2,root);
        inorder(root);
    }
}

三、红黑树

3.1红黑树的性质和定义?

红黑树相较于前两种树理解起来就比较麻烦了
红黑树是一种含有红黑结点并能自平衡的二叉查找树。性质:

  • 根节点是黑色。
  • 每个叶子节点都是空节点且都是黑色。
  • 每个红色节点的两个子节点一定都是黑色。
  • 任一节点到每个叶子节点的路径都包含数量相同的黑节点。
    在进行插入和删除等可能会破坏树的平衡的操作时,红黑树可以重新自处理达到平衡状态。

    由第三条可以推出如果一个节点存在黑子节点,那么该节点肯定存在两个子节点。

    在这里插入图片描述

黑色完美平衡: 任一节点到每个叶子节点的路径都包含有相同个数的黑节点。

红黑树的节点定义?

在这里插入图片描述
Entry就是TreeMap的内部红黑树实现类,节点由key和value组成。

3.2红黑树自平衡的条件?

三种操作:

  • 左旋: 以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变。如图 3。
  • 右旋: 以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变。如图 4。
  • 变色: 结点的颜色由红变黑或由黑变红。
    左旋只改变旋转节点和右子树的结构(父节点以上的结构不变),右旋只改变旋转节点和左子树的结构(父节点以上的结构不变)

3.3红黑树的查找

数据的查找与BST树的查找相同,且不会改变树的结构。
在这里插入图片描述

3.4红黑树的插入

在这里插入图片描述
插入操作节点的叫法约定:
在这里插入图片描述

插入节点应该是什么颜色?

红色!红色在父节点(如果存在)为黑色节点时,红黑树的黑色平衡没被破坏,不需要做自平衡操作。

情景一:红黑树为空树
直接把插入节点作为根节点,且将根节点设为黑色!
情景二:插入节点的key已经存在
把I设为当前节点的颜色,更新新节点的value为插入节点的value。
情景三:插入节点的父节点是黑色
由于插入的节点是红色的,父节点是黑色并不会影响黑平衡,直接插入!
情景四:插入节点的父节点是红色
由于根节点必须是黑色,当插入节点的父节点是红色的时候其父节点一定不是根节点。所以插入节点总是存在祖父节点。
  • 4.1叔叔节点存在且为红色
    祖父节点一定是黑色,因为不可能存在两个相连的红节点。
    处理方式:黑红红——》红黑红
    在这里插入图片描述
    如果PP节点的父节点是黑色就不用做任何操作。但如果是红色就需要把PP节点当作新插入节点进行平衡处理。
    当PP节点为根节点时,需重新把PP设为黑色,此时就成了黑黑红。

  • 4.2叔叔节点不存在或为黑节点,且插入结点的父亲结点是祖父结点的左子结点。
    在这里插入图片描述
    从插入前来看,没有I节点时,如果叔叔节点非红,则叔叔节点必是叶子节点(Nil)(走右子树路径的黑节点比走左子树路径的黑节点多),这种情况下插入节点就需要自平衡了。

  • 4.2.1插入节点是其父节点的左子节点
    将P设置为黑色,PP设置为红色,右旋。
    在这里插入图片描述

  • 4.2.2插入节点是其父节点的右子节点
    在这里插入图片描述

  • 4.3叔叔节点不存在或为黑节点,且插入结点的父亲结点是祖父结点的右子结点。

  • 4.3.1插入节点是其父节点的右子节点
    P设为黑色,PP设为红色,直接左旋。在这里插入图片描述

  • 4.3.1插入节点是其父节点的左子节点
    在这里插入图片描述
    到此插入的所有情况已经罗列出来。

3.5红黑树的删除

  1. 查找目标节点
  2. 删除后自平衡
    二叉树删除结点找替代结点有 3 种情景:
  • 若删除结点无子结点,直接删除。
  • 若删除结点只有一个子结点,用子结点替换删除结点。
  • 若删除结点有两个子结点,用后继结点(大于删除结点的最小结点)替换删除结点。
情景一:
直接删除
情景二:
删除节点用其唯一的子节点替换(不考虑键值情况下认为删除了子节点)
情景三:
后继节点:删除节点右子树的最左节点
前继节点:删除节点左子树的最右节点

在这里插入图片描述
删除节点的前后节点就是前/后继节点
在这里插入图片描述
重要思路:
重要思路:删除结点被替代后,在不考虑结点的键值的情况下,对于树来说,可以认为删除的是替代结点!


情景一二三都可以按照情景三的思路来,比如如果P节点没有右子节点,那么K就是替代节点(情景二),然后把K看作要删除的节点,这时候就找到了M(情景三),M没有左右子节点,此时M就是被删除的节点(情景一)(P的前继节点)

在这里插入图片描述
删除前先找到替换节点,然后把替换节点移到删除节点的位置进行覆盖,替换节点处成了空。

删除情景一:替换结点是红色结点。
替换节点一定是尾部节点,红色节点删除了也不会对平衡产生什么影响。

在这里插入图片描述

删除情景二:替换结点是黑色结点。

黑色节点被删除了得进行自平衡处理。还得考虑替换节点是父节点的左子节点还是右子节点。

删除情景2.1:替换结点是父结点的左子节点。

删除情景2.1.1:替换结点的兄弟节点是红节点。

在这里插入图片描述
兄弟结点是红结点,兄弟结点的父结点和子结点肯定为黑色,不会有其他子情景。先变色再左旋。旋转后的节点颜色如图。

删除情景2.1.2:替换结点的兄弟节点是黑节点。

此时其父节点和兄弟节点的子节点颜色也无法确认。

删除情景2.1.2.1:替换结点的兄弟节点的右子节点是红节点,左子节点任意颜色

在这里插入图片描述
先改变颜色再左旋。

删除情景2.1.2.2:替换结点的兄弟节点的右子节点是黑节点,左子节点为红色

在这里插入图片描述

删除情景2.1.2.3:替换结点的兄弟节点的子节点都是黑色

把兄弟结点设为红色,再把父结点当作替代结点,自底向上处理,去找父结点的兄弟结点去“借”。
在这里插入图片描述

删除情景2.2:替换结点是父结点的右子节点。
删除情景2.2.1:替换结点的兄弟节点是红节点

在这里插入图片描述

删除情景2.2.2:替换结点的兄弟节点是黑节点
删除情景2.2.2.1:替换结点的兄弟节点的左子节点是红节点,右子节点任意颜色

在这里插入图片描述

删除情景2.2.2.2:替换结点的兄弟节点的左子节点是黑节点,右子节点为红节点

在这里插入图片描述

删除情景2.2.2.3:替换结点的兄弟节点的子节点都是黑节点

在这里插入图片描述
将 S 设为红色,把 P 作为新的替换结点,重新进行删除结点情景处理。

综上,红黑树删除后自平衡的处理可以总结为:

  • 自己能搞定的自消化(情景 1)
  • 自己不能搞定的叫兄弟帮忙(除了情景 1、情景 2.1.2.3 和情景 2.2.2.3)
  • 兄弟都帮忙不了的,通过父母,找远方亲戚(情景2.1.2.3和情景2.2.2.3)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值