集合进阶6——TreeMap底层原理

一、HashMap

1.1 成员变量与构造方法

每一个Entry对象内部的属性有:key(键)、value(值)、left(左子节点地址值)、right(右子节点地址值)、color(该节点的颜色)。

TreeMap中的成员变量目前需要了解的三个:

comparator比较的规则;

root 记录根节点的地址值;

size 表示集合的长度,也表示红黑节点的个数;

空参构造中没有传递任何的数据。

comparator = null ; 表示没有任何比较器对象。

带参构造:我们需要把比较器对象传递给其参数,然后赋值给我们的成员变量。

1.2 添加元素分析

1.2.1 首次添加元素

在添加的时候,传递两个参数(K key,V value); 其方法内部又调用了三个参数

① 查看重写put的方法体,root在此之前已经说过是全局变量(成员变量),表示根节点的地址值。这里是模拟首次添加元素,所以此时局部变量t记录的是null。

② 对t的是否空的if判断语句,其中addEntryToEmptyMap(key,value)方法是将其首次添加的元素设置为根节点。return null 之后下面的代码就不走了。

addEntryToEmptyMap(key,value)其底层源码:

compare(key, key) :调用 compare 方法,传入相同的 key 作为参数。这通常用于类型检查(确保 key 的类型正确)和可能的空值检查。

核心逻辑:

创建了一个Entry的对象(Entry是TreeMap的一个内部类,创建类的对象),并且把这个地址值赋值给了root根节点(parent null)。同时将size长度设置为1,表示已经添加成功集合中已经有一个元素存在。

modCount 通常用于记录映射的修改次数,以便在迭代过程中检测是否发生了结构上的修改。

1.2.2 两次添加元素

第二次添加元素,此时root根节点已经有了元素,if判断语句不成立,直接执行下面的代码。

① cpm:表示两个键比较之后的结果。它是通过 comparator(比较器)的 compare 方法来比较 keyt.key 之后得到的整数值。

  • 如果 cmp < 0,表示 key 小于 t.key,代码会进入左子树(t = t.left;)。
  • 如果 cmp > 0,表示 key 大于 t.key,代码会进入右子树(t = t.right;)。
  • 如果 cmp == 0,表示 key 等于 t.key,此时会进行值的替换或返回旧值。

② Entrt<K,V> parent ; 当我们添加第二个元素,是成为根节点的左子节点还是右子节点呢?现在目前不知道啊,但是唯一能确定的就是其父节点一定就是根节点!这里parent获取的就是根节点root的地址值。

③ cpr:记录比较规则 (Comparator 是 Java 中的一个接口,用于定义对象之间的比较规则。在对集合(如 List)进行排序时,可以使用 Comparator 来定义自定义的排序规则。)

④ if语句判断是否有比较器对象。

这里看空参,即没有传递比较器的情况,其实两者代码大致相同,搞明白即可。

⑤ 把"键"进行强转,强转成comparable类型的 (Comparable 类型是 Java 中的一个接口,它允许对象进行自然排序。具体来说,一个类如果实现了 Comparable 接口,就必须提供一个 compareTo 方法,该方法定义了对象之间的比较规则。)

do - while循环不断的去找,判断条件为t != null 表示根节点的右子节点指向的是null停止循环,

注意:这里t 就像是一个比较器的手电筒(指引针),如果插入的元素比根节点大,去右边找,就能找到这个节点!

t 是记录我们root根节点的临时变量 赋值 给我们parent,意思是把root根节点当作当前节点的父节点。

调用compareTo方法调用根节点t.key的键值跟 当前k添加的节点的 键 按照cmp 比较器的方式进行比较。

如果为负数,去左子节点找;

如果为正数,去右子节点找;

如果为0,覆盖旧有的"值"数据,具体:

  • 获取旧值 oldValue = t.value
  • 如果 replaceOldtrueoldValuenull,则用新值替换旧值
  • 返回旧值 return oldValue;(表示覆盖了旧值)

addEntey()是具体的添加元素节点的方法。

1.3 第三次添加元素

假设第三次添加的元素大于上两次添加的元素,一样重复上述规则,此时形成的红黑树是违背红黑树规则的,

在addEntey()中具体还做了相应的调正:

首先还是创建了Entry()对象

如果你添加是父节点的左子节点,那么就把父节点的左子节点的left,记录为当前的节点e;

如果你添加是父节点的右子节点,那么就把父节点的右子节点的right,记录为当前的节点e。

互相记录,互相认亲,一个认亲老爸、一个认亲儿子。

fixAfterInsertion(e): 就是按照红黑树的规则进行调整。

fixAfterInsertion(e):

第一步就是调整颜色

后续代码:

循环条件

复制代码
1while (x != null && x != root && x.parent.color == RED) {
  • 条件 :当 x 不为 nullx 不是根节点且 x 的父节点为红色时,进入循环。
  • 目的:确保红黑树的性质(红色节点不能有红色的子节点)。

父节点是祖父节点的左子节点

复制代码
1if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
  • 条件x 的父节点是其祖父节点的左子节点。
  • 目的 :确定 x 在树中的具体位置,以便进行相应的调整。

叔叔节点为红色

复制代码
1Entry<K,V> y = rightOf(parentOf(parentOf(x)));
2if (colorOf(y) == RED) {
3    setColor(parentOf(x), BLACK);
4    setColor(y, BLACK);
5    setColor(parentOf(parentOf(x)), RED);
6    x = parentOf(parentOf(x));
7}
  • 条件x 的叔叔节点(祖父节点的右子节点)为红色。
  • 操作
    • x 的父节点和叔叔节点设为黑色。
    • 将祖父节点设为红色。
    • x 指向祖父节点,继续检查更高层的节点。

叔叔节点为黑色且 x 是右子节点

复制代码
1else {
2    if (x == rightOf(parentOf(x))) {
3        x = parentOf(x);
4        rotateLeft(x);
5    }
6    setColor(parentOf(x), BLACK);
7    setColor(parentOf(parentOf(x)), RED);
8    rotateRight(parentOf(parentOf(x)));
9}
  • 条件x 的叔叔节点为黑色且 x 是其父节点的右子节点。
  • 操作
    • x 指向其父节点。
    • x 进行左旋转。
    • x 的父节点设为黑色,祖父节点设为红色。
    • 对祖父节点进行右旋转。

父节点是祖父节点的右子节点

(代码结构与上述类似,但方向相反)

最终操作

复制代码
1root.color = BLACK;
  • 操作:将根节点设为黑色,确保红黑树的性质。

声明:

分析借鉴于通义AI

以上均来源于B站@ITheima的教学内容!!!

本人跟着视频内容学习,整理知识引用

相关推荐
普通网友1 小时前
内存对齐与缓存友好设计
开发语言·c++·算法
JEECG低代码平台1 小时前
GitHub 十大 Java 语言 AI 开源项目推荐
java·人工智能·github
小咖张1 小时前
idea 启动失败,不加载自己的配置文件
java·ide·intellij-idea
lsx2024061 小时前
DOM 节点信息
开发语言
m***11901 小时前
使用IDEA环境编译Spring源码及spring源码调试环境搭建
java·spring·intellij-idea
普通网友2 小时前
C++编译期数据结构
开发语言·c++·算法
代码程序猿RIP2 小时前
【C++开发面经】全过程面试问题详解
java·c++·面试
whatever who cares2 小时前
Java/Android中BigDecimal的相关操作
android·java·开发语言
im_AMBER2 小时前
算法笔记 13 BFS | 图
笔记·学习·算法·广度优先