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


插入元素

插入元素,如果元素在树中存在,则替换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. 关于JS前台计算四舍五入的问题

    //除法函数 //说明:javascript的除法结果会有误差,在两个浮点数相除的时候会比较明显.这个函数返回较为精确的除法结果. //调用:accDiv(arg1,arg2) //返回值:arg1除 ...

  2. 利用 Saltstack 远程执行命令

    Saltstack的一个比较突出优势就是具备执行远程命令的功能. 操作方法与func (https://fedorahosted.org/func/)相似,可以帮助运维人员完成集中化的操作平台. ht ...

  3. Currency Exchange POJ - 1860 (spfa判断正环)

    Several currency exchange points are working in our city. Let us suppose that each point specializes ...

  4. C#代码总结04---通过创建临时表DataTable进行临时编辑删除

    <script type="text/javascript"> //删除 function Delete(hdGuid) { $("#hdGuid" ...

  5. 简单的NIO使用实例

    public class ThreadTest_2 { public static void main(String[] args) { Thread downloaderThread = null; ...

  6. centos 桥接配置 设置网络代理 lnmp搭建

    一.桥接配置 centos设置  编辑->虚拟网络编辑器->桥接模式->还原默认设置 虚拟机->设置->网络适配器->桥接 cd /etc/sysconfig/ne ...

  7. 如何使用$.each()与$().each()以及他们的区别

    1.首先,说下$.each(Arry/Object,function(index,val){ //index表示下标,val表示下标对应的值 }) 下面是使用$.each()的几种类型,其中arr2与 ...

  8. [转]XModem协议

    出处:XModem协议 XModem协议介绍:XModem是一种在串口通信中广泛使用的异步文件传输协议,分为XModem和1k-XModem协议两种,前者使用128字节的数据块,后者使用1024字节即 ...

  9. dedecms 后台可以上传mp4,但无法选择

    原文链接 找到 /include/dialog/select_media.php 找到rmvb,在其后面加 “|mp4” 即可. 1

  10. Maven工程无异常 启动没有出现Starting ProtocolHandler的原因

    这个情况可能的原因 一般来说有3种1.数据库没连接上2.注册中心没连接上3.逆向工程生成的mapper 有问题解决:哪个Maven工程出问题,就那个工程的src/main/resource目录下面添加 ...