死磕 java集合之TreeMap源码分析(二)- 内含红黑树分析全过程
欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。
插入元素
插入元素,如果元素在树中存在,则替换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条特性,那么我们只要根据这个特性来平衡就容易多了。
根据不同的情况有以下几种处理方式:
插入的元素如果是根节点,则直接涂成黑色即可,不用平衡;
插入的元素的父节点如果为黑色,不需要平衡;
插入的元素的父节点如果为红色,则违背了特性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源码分析(二)- 内含红黑树分析全过程的更多相关文章
- 死磕 java集合之TreeMap源码分析(四)-内含彩蛋
欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 二叉树的遍历 我们知道二叉查找树的遍历有前序遍历.中序遍历.后序遍历. (1)前序遍历,先遍历 ...
- 死磕 java集合之TreeMap源码分析(一)- 内含红黑树分析全过程
欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 简介 TreeMap使用红黑树存储元素,可以保证元素按key值的大小进行遍历. 继承体系 Tr ...
- 死磕 java集合之TreeMap源码分析(三)- 内含红黑树分析全过程
欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 删除元素 删除元素本身比较简单,就是采用二叉树的删除规则. (1)如果删除的位置有两个叶子节点 ...
- 死磕 java集合之DelayQueue源码分析
问题 (1)DelayQueue是阻塞队列吗? (2)DelayQueue的实现方式? (3)DelayQueue主要用于什么场景? 简介 DelayQueue是java并发包下的延时阻塞队列,常用于 ...
- 死磕 java集合之PriorityBlockingQueue源码分析
问题 (1)PriorityBlockingQueue的实现方式? (2)PriorityBlockingQueue是否需要扩容? (3)PriorityBlockingQueue是怎么控制并发安全的 ...
- 死磕 java集合之PriorityQueue源码分析
问题 (1)什么是优先级队列? (2)怎么实现一个优先级队列? (3)PriorityQueue是线程安全的吗? (4)PriorityQueue就有序的吗? 简介 优先级队列,是0个或多个元素的集合 ...
- 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计
问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...
- 死磕 java集合之LinkedHashSet源码分析
问题 (1)LinkedHashSet的底层使用什么存储元素? (2)LinkedHashSet与HashSet有什么不同? (3)LinkedHashSet是有序的吗? (4)LinkedHashS ...
- 死磕 java集合之ConcurrentHashMap源码分析(三)
本章接着上两章,链接直达: 死磕 java集合之ConcurrentHashMap源码分析(一) 死磕 java集合之ConcurrentHashMap源码分析(二) 删除元素 删除元素跟添加元素一样 ...
随机推荐
- net core体系-Standard-1概述
前言 早上起来.NET社区沸腾了,期待已久的.NET Core 2.0终于发布!根据个人经验,微软的产品一般在2.0时会趋于成熟,所以一个新的.Net开发时代已经来临!未来属于.NET Core. . ...
- 小程序-canvas在IOS手机层级最高无法展示问题
要求的效果: 正面: 背面: 在开发者工具 利用css 和定位实现了一个版本 .topBox.on { transform:rotateY(180deg); } .topBox { position: ...
- python--random库基本介绍
random库是使用随机数的Python标准库 python中用于生成伪随机数的函数库是random 因为是标准库,使用时候只需要import random random库包含两类函数,常用的共9个 ...
- c++继承学习
继承分类: 虚表继承 class D{ public : d(){ } ~d(){ } private: }; 单重继承 class D{ public : d(){ } ~d(){ } privat ...
- Linux 体系结构
Linux 体系结构 Linux 嵌入式系统的组成 层次结构图 bios 1.硬件检测 2.初始化系统设备 3.装入os 4.调os向硬件发出的指令 bsp 板级支持包 硬件相关 开发板原理图 开 ...
- Windows本地代码仓库使用连接教程
目录 软件安装 修改语言为中文 克隆远程仓库 文件上传教程 软件安装 安装Git(软件下载链接) 根据自己的系统选择对应版本下载安装 安装TortoiseGit(软件下载链接) 1.下载完毕解压文件夹 ...
- Linux内核内存管理架构
内存管理子系统可能是linux内核中最为复杂的一个子系统,其支持的功能需求众多,如页面映射.页面分配.页面回收.页面交换.冷热页面.紧急页面.页面碎片管理.页面缓存.页面统计等,而且对性能也有很高的要 ...
- centos7 安装python3.7.11 笔记
安装python依赖包yum install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-deve ...
- angular.module()
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- [LeetCode] Mirror Reflection 镜面反射
There is a special square room with mirrors on each of the four walls. Except for the southwest cor ...