欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。


插入元素

插入元素,如果元素在树中存在,则替换value;如果元素不存在,则插入到对应的位置,再平衡树。

public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
// 如果没有根节点,直接插入到根节点
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
// key比较的结果
int cmp;
// 用来寻找待插入节点的父节点
Entry<K,V> parent;
// 根据是否有comparator使用不同的分支
Comparator<? super K> cpr = comparator;
if (cpr != null) {
// 如果使用的是comparator方式,key值可以为null,只要在comparator.compare()中允许即可
// 从根节点开始遍历寻找
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
// 如果小于0从左子树寻找
t = t.left;
else if (cmp > 0)
// 如果大于0从右子树寻找
t = t.right;
else
// 如果等于0,说明插入的节点已经存在了,直接更换其value值并返回旧值
return t.setValue(value);
} while (t != null);
}
else {
// 如果使用的是Comparable方式,key不能为null
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
// 从根节点开始遍历寻找
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
// 如果小于0从左子树寻找
t = t.left;
else if (cmp > 0)
// 如果大于0从右子树寻找
t = t.right;
else
// 如果等于0,说明插入的节点已经存在了,直接更换其value值并返回旧值
return t.setValue(value);
} while (t != null);
}
// 如果没找到,那么新建一个节点,并插入到树中
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
// 如果小于0插入到左子节点
parent.left = e;
else
// 如果大于0插入到右子节点
parent.right = e; // 插入之后的平衡
fixAfterInsertion(e);
// 元素个数加1(不需要扩容)
size++;
// 修改次数加1
modCount++;
// 如果插入了新节点返回空
return null;
}

插入再平衡

插入的元素默认都是红色,因为插入红色元素只违背了第4条特性,那么我们只要根据这个特性来平衡就容易多了。

根据不同的情况有以下几种处理方式:

  1. 插入的元素如果是根节点,则直接涂成黑色即可,不用平衡;

  2. 插入的元素的父节点如果为黑色,不需要平衡;

  3. 插入的元素的父节点如果为红色,则违背了特性4,需要平衡,平衡时又分成下面三种情况:

(如果父节点是祖父节点的左节点)

情况 策略
1)父节点为红色,叔叔节点也为红色 (1)将父节点设为黑色;
(2)将叔叔节点设为黑色;
(3)将祖父节点设为红色;
(4)将祖父节点设为新的当前节点,进入下一次循环判断;
2)父节点为红色,叔叔节点为黑色,且当前节点是其父节点的右节点 (1)将父节点作为新的当前节点;
(2)以新当节点为支点进行左旋,进入情况3);
3)父节点为红色,叔叔节点为黑色,且当前节点是其父节点的左节点 (1)将父节点设为黑色;
(2)将祖父节点设为红色;
(3)以祖父节点为支点进行右旋,进入下一次循环判断;

(如果父节点是祖父节点的右节点,则正好与上面反过来)

情况 策略
1)父节点为红色,叔叔节点也为红色 (1)将父节点设为黑色;
(2)将叔叔节点设为黑色;
(3)将祖父节点设为红色;
(4)将祖父节点设为新的当前节点,进入下一次循环判断;
2)父节点为红色,叔叔节点为黑色,且当前节点是其父节点的左节点 (1)将父节点作为新的当前节点;
(2)以新当节点为支点进行右旋;
3)父节点为红色,叔叔节点为黑色,且当前节点是其父节点的右节点 (1)将父节点设为黑色;
(2)将祖父节点设为红色;
(3)以祖父节点为支点进行左旋,进入下一次循环判断;

让我们来看看TreeMap中的实现:

/**
* 插入再平衡
*(1)每个节点或者是黑色,或者是红色。
*(2)根节点是黑色。
*(3)每个叶子节点(NIL)是黑色。(注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!)
*(4)如果一个节点是红色的,则它的子节点必须是黑色的。
*(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
*/
private void fixAfterInsertion(Entry<K,V> x) {
// 插入的节点为红节点,x为当前节点
x.color = RED; // 只有当插入节点不是根节点且其父节点为红色时才需要平衡(违背了特性4)
while (x != null && x != root && x.parent.color == RED) {
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
// a)如果父节点是祖父节点的左节点
// y为叔叔节点
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
// 情况1)如果叔叔节点为红色
// (1)将父节点设为黑色
setColor(parentOf(x), BLACK);
// (2)将叔叔节点设为黑色
setColor(y, BLACK);
// (3)将祖父节点设为红色
setColor(parentOf(parentOf(x)), RED);
// (4)将祖父节点设为新的当前节点
x = parentOf(parentOf(x));
} else {
// 如果叔叔节点为黑色
// 情况2)如果当前节点为其父节点的右节点
if (x == rightOf(parentOf(x))) {
// (1)将父节点设为当前节点
x = parentOf(x);
// (2)以新当前节点左旋
rotateLeft(x);
}
// 情况3)如果当前节点为其父节点的左节点(如果是情况2)则左旋之后新当前节点正好为其父节点的左节点了)
// (1)将父节点设为黑色
setColor(parentOf(x), BLACK);
// (2)将祖父节点设为红色
setColor(parentOf(parentOf(x)), RED);
// (3)以祖父节点为支点进行右旋
rotateRight(parentOf(parentOf(x)));
}
} else {
// b)如果父节点是祖父节点的右节点
// y是叔叔节点
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
// 情况1)如果叔叔节点为红色
// (1)将父节点设为黑色
setColor(parentOf(x), BLACK);
// (2)将叔叔节点设为黑色
setColor(y, BLACK);
// (3)将祖父节点设为红色
setColor(parentOf(parentOf(x)), RED);
// (4)将祖父节点设为新的当前节点
x = parentOf(parentOf(x));
} else {
// 如果叔叔节点为黑色
// 情况2)如果当前节点为其父节点的左节点
if (x == leftOf(parentOf(x))) {
// (1)将父节点设为当前节点
x = parentOf(x);
// (2)以新当前节点右旋
rotateRight(x);
}
// 情况3)如果当前节点为其父节点的右节点(如果是情况2)则右旋之后新当前节点正好为其父节点的右节点了)
// (1)将父节点设为黑色
setColor(parentOf(x), BLACK);
// (2)将祖父节点设为红色
setColor(parentOf(parentOf(x)), RED);
// (3)以祖父节点为支点进行左旋
rotateLeft(parentOf(parentOf(x)));
}
}
}
// 平衡完成后将根节点设为黑色
root.color = BLACK;
}

插入元素举例

我们依次向红黑树中插入 4、2、3 三个元素,来一起看看整个红黑树平衡的过程。

三个元素都插入完成后,符合父节点是祖父节点的左节点,叔叔节点为黑色,且当前节点是其父节点的右节点,即情况2)。

情况2)需要做以下两步处理:

(1)将父节点作为新的当前节点;

(2)以新当节点为支点进行左旋,进入情况3);

情况3)需要做以下三步处理:

(1)将父节点设为黑色;

(2)将祖父节点设为红色;

(3)以祖父节点为支点进行右旋,进入下一次循环判断;

下一次循环不符合父节点为红色了,退出循环,插入再平衡完成。


未完待续,下一节我们一起探讨红黑树删除元素的操作。

现在公众号文章没办法留言了,如果有什么疑问或者建议请直接在公众号给我留言。


欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。

死磕 java集合之TreeMap源码分析(二)- 内含红黑树分析全过程的更多相关文章

  1. 死磕 java集合之TreeMap源码分析(四)-内含彩蛋

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 二叉树的遍历 我们知道二叉查找树的遍历有前序遍历.中序遍历.后序遍历. (1)前序遍历,先遍历 ...

  2. 死磕 java集合之TreeMap源码分析(一)- 内含红黑树分析全过程

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 简介 TreeMap使用红黑树存储元素,可以保证元素按key值的大小进行遍历. 继承体系 Tr ...

  3. 死磕 java集合之TreeMap源码分析(三)- 内含红黑树分析全过程

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 删除元素 删除元素本身比较简单,就是采用二叉树的删除规则. (1)如果删除的位置有两个叶子节点 ...

  4. 死磕 java集合之DelayQueue源码分析

    问题 (1)DelayQueue是阻塞队列吗? (2)DelayQueue的实现方式? (3)DelayQueue主要用于什么场景? 简介 DelayQueue是java并发包下的延时阻塞队列,常用于 ...

  5. 死磕 java集合之PriorityBlockingQueue源码分析

    问题 (1)PriorityBlockingQueue的实现方式? (2)PriorityBlockingQueue是否需要扩容? (3)PriorityBlockingQueue是怎么控制并发安全的 ...

  6. 死磕 java集合之PriorityQueue源码分析

    问题 (1)什么是优先级队列? (2)怎么实现一个优先级队列? (3)PriorityQueue是线程安全的吗? (4)PriorityQueue就有序的吗? 简介 优先级队列,是0个或多个元素的集合 ...

  7. 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计

    问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...

  8. 死磕 java集合之LinkedHashSet源码分析

    问题 (1)LinkedHashSet的底层使用什么存储元素? (2)LinkedHashSet与HashSet有什么不同? (3)LinkedHashSet是有序的吗? (4)LinkedHashS ...

  9. 死磕 java集合之ConcurrentHashMap源码分析(三)

    本章接着上两章,链接直达: 死磕 java集合之ConcurrentHashMap源码分析(一) 死磕 java集合之ConcurrentHashMap源码分析(二) 删除元素 删除元素跟添加元素一样 ...

随机推荐

  1. 神器PHPStorm个人最常用快捷键

    PHPStorm是目前PHP开发者最常用的IDE之一,善用快捷键可以极大地提升效率,网上有很多盘点,多而全,但很多要么不实用,要么操作不方便,下面盘点一下个人平时最常用的,亲测有效. CTRL+N 查 ...

  2. 安装docker以及问题解决办法

    1.使用官方推荐的方式安装 yum-utilsyum install -y yum-utils2.使用如下的命令设置稳定版的 repositoryyum-config-manager \    --a ...

  3. 数据库SQLServr安装时出现--"需要更新以前的Visual Studio 2010实例"--状态失败

    在电脑中安装过Visual Studio比较低版本的软件的时候 将原本的Microsoft Visual Studio 2010 Service Pack 1进行了更改 导致sql比较高版本的不能很好 ...

  4. BZOJ 4804

    辣鸡题目毁我青春 易推 \[\sum_{i=1}^n\sum_{i=1}^m \varphi(gcd(i,j))=\sum_{T}\frac{n}{T}\dfrac{m}{T}\sum_{d|T} \ ...

  5. CoreException: Could not get the value for parameter compilerId for plugin execution default-compile: PluginResolutionException: Plugin org.apache.maven.plugins:maven-compiler-plugin:3.1

    今天遇到一个奇怪的问题, 之前写好的代码, 更换环境后, 重新搭建的nexus, maven私服总是报错, 各种clean/update都不管用 原来是没写版本号, 后来加上3.1版本, 还是报错, ...

  6. 13-事务&数据库连接池&DBUtiles

    事务&数据库连接池&DBUtils 事务 Transaction  其实指的一组操作,里面包含许多个单一的逻辑.只要有一个逻辑没有执行成功,那么都算失败. 所有的数据都回归到最初的状态 ...

  7. jdk12+tomcat9 配置

    jdk12 没有了jre的配置   直接配置path就可以了 tomcat常规配置,只是启动的时候麻烦一点   需要重新写入jdk jre jvm 到server.bat文件 参考文章: https: ...

  8. java中bigInteger的应用

    BigInteger abs()  返回大整数的绝对值BigInteger add(BigInteger val) 返回两个大整数的和BigInteger and(BigInteger val)  返 ...

  9. H5_ 多媒体video,autio使用示例

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. docker 数据卷管理

    在生产环境中使用docker,往往需要对数据进行持久化,或者需要在多个容器之间进行数据共享,这涉及到容器对数据管理的操作 容器对数据的管理主要有两种方式: 数据卷(Data Volumes): 容器内 ...