HashMap源码(二)——红黑树(TreeMap 源码)详解。插入。删除(暂时未写)

注意!!!
红黑树源码使用TreeMap源码里的红黑树来做笔记,原因是TreeMap的红黑树的名称起的好一点,代码清晰易于理解。

红黑树维基百科

红黑树可视化

可视化不需要外网访问(这个可视化和TreeMap的代码逻辑有一点点出入,但看TreeMap的代码是可以理解的。这个可视化我认为做的挺好的,这个网站还有其他算法的可视化,可以自己找找)

红黑树性质

红黑树就是二叉查找树

  • 每个节点不是红色就是黑色。
  • 不可能有连在一起的红色的节点。
  • 根节点都是黑色。
  • 每个红色节点的两个子节点都是黑色。叶子节点(NULL)都是黑色。
  • 从任一节点到每个叶子节点的都包含相同数目的黑色节点(称为黑高【黑色高度完美平衡】)

满足这些性质就可以近似平衡了,

要满足上述性质,需要执行2种行为

插入

这里仅给出TreeMap插入节点到合适位置(不考虑变色和旋转,仅是根据值的大小放到位置)后,对节点的修复代码,后面的案例主要也是表现的修复过程(变色和旋转)。

TreeMap的插入节点后的修复插入(变色、旋转)源码

private void fixAfterInsertion(Entry<K,V> x) {
    x.color = RED;

    while (x != null && x != root && x.parent.color == RED) {
        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
            Entry<K,V> y = rightOf(parentOf(parentOf(x)));
            if (colorOf(y) == RED) {
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            } else {
                if (x == rightOf(parentOf(x))) {
                    x = parentOf(x);
                    rotateLeft(x);
                }
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                rotateRight(parentOf(parentOf(x)));
            }
        } else {
            Entry<K,V> y = leftOf(parentOf(parentOf(x)));
            if (colorOf(y) == RED) {
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            } else {
                if (x == leftOf(parentOf(x))) {
                    x = parentOf(x);
                    rotateRight(x);
                }
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                rotateLeft(parentOf(parentOf(x)));
            }
        }
    }
    root.color = BLACK;
}
/*
// 插入测试代码 // 插入key的顺序是:1,10,2,9,3,8,4,7,5,6
        TreeMap<Integer, String> treeMap = new TreeMap<>();
        for (int i = 1, x = 1,y=10; i< 11;i++){
            if (i % 2 != 0) {
                treeMap.put(x, x + "Value");
                x++;
            } else {
                treeMap.put(y, y + "Value");
                y--;
            }
        }
*/

插入案例前述:

插入的注意事项:每次插入的数据的节点颜色都是红色,这样可以满足上述最后一条性质,尽量减少调整次数。

插入key的值是:1,10,2,9,3,8,4,7,5,6

5,6(忽略,给我写累了)

因为红黑树的难点在于6种情况的调整,所以最好结合源码来看下面的示例插入。可以先看下总结,脑袋中有个变色和旋转情况的轮廓。

插入节点1

因为是根节点,红变黑

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uowmei8Y-1622823230959)(images/image-20210604224939186.png)]

插入节点10

  • 初始10是红色节点
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iT9dt6CI-1622823230962)(images/image-20210604225019519.png)]

  • 插入10到合适的位置(不考虑变色,和调整),指针指向10。
    在这里插入图片描述

  • 判断是否进入循环(指针指向节点不是根节点,且指针指向节点的父节点是红色,则需要变色或调整)

    不进入循环。因为指针指向的结点10不满足循环条件。

  • 置根节点为黑色

插入节点2

  • 初始2是红色节点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zYgIiink-1622823230964)(images/image-20210604225353925.png)]

  • 插入2到合适位置(不考虑变色,和调整),指针指向节点2。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JOtLbfjB-1622823230965)(images/image-20210604225432354.png)]

  • 判断是否进入循环(指针指向节点不是根节点,且指针指向节点的父节点是红色,则需要变色或调整)。

    进入循环,因为指针指向的节点2满足循环条件。

    叔叔节点是插入节点的爷爷节点的左孩子

    • 将指针指向原来指针节点的父节点,也就是节点10,以节点10,进行右旋。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7nXzFwuk-1622823230966)(images/image-20210604225812875.png)]

    • 将父节点设为黑色,爷爷节点设为红色,左旋指针指向节点的爷爷节点

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7Ktegthz-1622823230967)(images/image-20210604230022554.png)]

  • 判断是否进入循环(指针指向节点不是根节点,且指针指向节点的父节点是红色,则需要变色或调整)。

    不进入循环,因为指针指向的节点10,不满足循环条件。

  • 置根节点为黑色。

插入节点9

  • 初始节点9为红色

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Atg9oCxQ-1622823230969)(images/image-20210604230151499.png)]

  • 插入9到合适位置(不考虑变色,和调整),节点指针指向9节点

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8IxMxJMj-1622823230970)(images/image-20210604230230478.png)]

  • 判断是否进入循环(指针指向节点不是根节点,且指针指向节点的父节点是红色,则需要变色或调整)。

    进入循环,因为指针指向的节点9满足循环条件。

    指针指向节点的叔叔节点是红色。

    • 叔叔节点设为黑色,父亲节点设为黑色,爷爷节点设为红色,将指针指向爷爷节点。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pmix5o1D-1622823230971)(images/image-20210604230502507.png)]

  • 判断是否进入循环(循环条件是:指针指向的节点不是根节点,且父节点不是红色)。
    不进入循环,指针指向节点2不满足循环条件

  • 置根节点为黑色
    在这里插入图片描述

插入节点3

  • 初始节点3是红色节点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CfbpcMac-1622823230973)(images/image-20210604230834565.png)]

  • 将节点3放到合适的位置(不考虑变色,和调整),节点指针指向3节点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-evDQkfWA-1622823230974)(images/image-20210604230900617.png)]

  • 判断是否进入循环(循环条件是:指针指向的节点不是根节点,且父节点不是红色)

    进入循环,因为指针指向节点是节点3满足循环条件。

    指针指向节点的叔叔节点是黑色

    • 置父节点黑色,爷爷节点红色,右旋爷爷节点

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RqTvzDmU-1622823230975)(images/image-20210604231359570.png)]

  • 判断是否进入循环(循环条件是:指针指向的节点不是根节点,且父节点不是红色)

    不进入循环,指针指向的节点是3,节点3不满足循环条件。

  • 置根节点为黑色。

插入节点8

  • 初识插入节点8

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MPH1WAf7-1622823230980)(images/image-20210604231604131.png)]

  • 调整到合适位置(不考虑变色,和调整),节点指针指向8节点
    在这里插入图片描述

  • 判断是否进入循环(循环条件是:指针指向的节点不是根节点,且父节点不是红色)
    进入循环,指针指向节点8满足循环条件
    叔叔节点是红色。

    • 设置父节点为黑色,设置叔叔节点为黑色,指针指向爷爷节点。
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zN3Rz3aJ-1622823230985)(images/image-20210604231909539.png)]
  • 判断是否进入循环(循环条件是:指针指向的节点不是根节点,且父节点不是红色)

    不进入循环,指针指向9,节点9不满足循环条件

  • 置根节点为黑色。

插入节点4

  • 初始节点4,为红色

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gOuJAk5s-1622823230986)(images/image-20210604232205906.png)]

  • 插入4到合适位置(不考虑变色,和调整),节点指针指向4节点

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eQ8Okbug-1622823230986)(images/image-20210604232247480.png)]

  • 判断是否进入循环。(循环条件是:指针指向的节点不是根节点,且父节点不是红色)

    进入循环,节点4满足循环条件

    节点4的叔叔节点是黑色。

    • 指针指向父节点8,右旋指针指向的节点8。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7YiS9qpj-1622823230987)(images/image-20210604232624412.png)]

    • 指针指向节点的父节点4设为黑色,爷爷节点3设为红色,左旋

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EeEofO4t-1622823230989)(images/image-20210604232707130.png)]

    • 判断是否进入循环。(循环条件是:指针指向的节点不是根节点,且父节点不是红色)

      不进入循环,指针指向的节点8不满足条件。

  • 置根节点为黑色。

插入节点7

  • 初识节点7为红色
    在这里插入图片描述

  • 插入节点7到合适位置(不考虑变色,和调整),节点指针指向7节点

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JWAHlpnh-1622823230991)(images/image-20210604232913706.png)]

  • 判断是否进入循环。(循环条件是:指针指向的节点不是根节点,且父节点不是红色)
    进入循环,指针指向节点7满足循环条件。
    7的叔叔节点是红色。

    • 设置叔叔节点和父亲节点为黑色。指针指向爷爷节点
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sr3QEJDu-1622823230992)(images/image-20210604233112950.png)]
  • 判断是否进入循环。(循环条件是:指针指向的节点不是根节点,且父节点不是红色)
    进入循环,指针指向的节点4满足循环条件。
    指针指向的节点4的叔叔节点是黑色。

    • 指针指向父亲节点,右旋
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M1AjJoKx-1622823230994)(images/image-20210604233232783.png)]

    • 指针指向的节点的父节点设为黑色,爷爷节点设为红色,左旋爷爷节点。
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EgHXgLuM-1622823230995)(images/image-20210604233431903.png)]

  • 判断是否进入循环。(循环条件是:指针指向的节点不是根节点,且父节点不是红色)
    不进入循环,因为节点9不满足条件。

  • 置根节点为黑色。

插入代码简单总结

什么时候退出循环调整(变色或旋转)

当前指针指向的节点不是根节点,且指针指向的节点的父节点不是红色。

什么时候进入循环调整(变色或旋转)

当前指针指向的节点不是根节点,且指针指向的节点的父节点是红色。

情况1:叔叔节点在指针指向节点的爷爷节点的左侧

情况1.1:叔叔节点是红色

设置父节点为黑色,
设置叔叔节点为黑色,
指针指向爷爷节点。

情况1.2:叔叔节点是黑色(分两种情况)
情况1.2.1:比较节点是父节点的左孩子

指针节点指向父节点,右旋指针节点后,执行情况1.2.2操作

情况1.2.2:比较节点是父节点的右孩子(腹[父]黑也能红)

指针节点的父节点设置为黑色
指针节点的爷爷节点设置为红色
左旋指针节点的爷爷节点

情况2:叔叔节点在指针节点的右侧

情况2.1:叔叔节点是红色(和情况1.1相同)

设置父节点为黑色,
设置叔叔节点为黑色,
指针节点指向爷爷节点。

情况2.2:叔叔节点是黑色(分两种情况)
情况2.2.1:指针节点是父节点的右孩子

指针节点指向父节点,左旋指针节点后执行情况2.2.1操作

情况2.2.1:比较节点是父节点的左孩子(腹[父]黑爷能红)

设置父节点颜色为黑色,
设置爷爷节点颜色为红色,
右旋爷爷节点。

插入总结简要摘要

叔叔节点在爷爷节点的左侧

  • 左叔红,设叔,父为黑,指爷。(左叔红:叔叔节点在指针节点的爷爷节点的左侧。设叔,父为黑:设指针节点的叔叔节点和父亲节点为黑色。指向爷:将指针指向指针节点的爷爷节点)
  • 左叔黑,左孩子,指父,右旋。(左孩子:指针节点是指针节点父节点的左孩子。右旋:右旋指针节点)
  • 左叔黑,右孩子,设父黑,设爷红,左旋爷。

叔叔节点在爷爷节点的右侧

  • 右叔红,设叔,父为黑,指向爷。
  • 右叔黑,右孩子,指父,左旋。
  • 右叔黑,左孩子,设父黑,设爷红,右旋爷。

删除(暂时未写)

常见问题

为什么要使用红黑树,而不使用AVL(平衡二叉树)作为Java实现代码的常用的数据结构

AVL树追求完美平衡,调整次数过多。

平衡二叉树的目的是不让二叉搜索树退化成单链表

为什么红黑树或者说二叉树不适用于数据库索引,而在HashMap里会用到。

二叉树的一个节点仅存一个值,访问一次索引就是在访问一次磁盘,读取磁盘次数过多。适用于HashMap是因为,HashMap的数据是在内存中存放。访问速度较快。

而解决这种磁盘访问次数过多的解决方案就是使用多叉树B+Tree。

PS

如果帮助到你的话,点个赞鼓励下,欢迎加入我的置顶博客的javaweb交流群,不搞带课推广,让我们一起向诗靠拢。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值