深读源码-java集合之TreeMap源码分析(二)

2023-11-07 09:32

本文主要是介绍深读源码-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) checkroot = 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不能为nullif (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++;// 修改次数加1modCount++;// 如果插入了新节点返回空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)。

1

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

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

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

2

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

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

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

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

3

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


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


原文链接:https://www.cnblogs.com/tong-yuan/p/10657274.html

这篇关于深读源码-java集合之TreeMap源码分析(二)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/362741

相关文章

Spring Boot整合Redis注解实现增删改查功能(Redis注解使用)

《SpringBoot整合Redis注解实现增删改查功能(Redis注解使用)》文章介绍了如何使用SpringBoot整合Redis注解实现增删改查功能,包括配置、实体类、Repository、Se... 目录配置Redis连接定义实体类创建Repository接口增删改查操作示例插入数据查询数据删除数据更

Java Lettuce 客户端入门到生产的实现步骤

《JavaLettuce客户端入门到生产的实现步骤》本文主要介绍了JavaLettuce客户端入门到生产的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要... 目录1 安装依赖MavenGradle2 最小化连接示例3 核心特性速览4 生产环境配置建议5 常见问题

Java使用Swing生成一个最大公约数计算器

《Java使用Swing生成一个最大公约数计算器》这篇文章主要为大家详细介绍了Java使用Swing生成一个最大公约数计算器的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下... 目录第一步:利用欧几里得算法计算最大公约数欧几里得算法的证明情形 1:b=0情形 2:b>0完成相关代码第二步:加

Java 的ArrayList集合底层实现与最佳实践

《Java的ArrayList集合底层实现与最佳实践》本文主要介绍了Java的ArrayList集合类的核心概念、底层实现、关键成员变量、初始化机制、容量演变、扩容机制、性能分析、核心方法源码解析、... 目录1. 核心概念与底层实现1.1 ArrayList 的本质1.1.1 底层数据结构JDK 1.7

Java Map排序如何按照值按照键排序

《JavaMap排序如何按照值按照键排序》该文章主要介绍Java中三种Map(HashMap、LinkedHashMap、TreeMap)的默认排序行为及实现按键排序和按值排序的方法,每种方法结合实... 目录一、先理清 3 种 Map 的默认排序行为二、按「键」排序的实现方式1. 方式 1:用 TreeM

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

C++中unordered_set哈希集合的实现

《C++中unordered_set哈希集合的实现》std::unordered_set是C++标准库中的无序关联容器,基于哈希表实现,具有元素唯一性和无序性特点,本文就来详细的介绍一下unorder... 目录一、概述二、头文件与命名空间三、常用方法与示例1. 构造与析构2. 迭代器与遍历3. 容量相关4

Java中Redisson 的原理深度解析

《Java中Redisson的原理深度解析》Redisson是一个高性能的Redis客户端,它通过将Redis数据结构映射为Java对象和分布式对象,实现了在Java应用中方便地使用Redis,本文... 目录前言一、核心设计理念二、核心架构与通信层1. 基于 Netty 的异步非阻塞通信2. 编解码器三、

SpringBoot基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

一篇文章彻底搞懂macOS如何决定java环境

《一篇文章彻底搞懂macOS如何决定java环境》MacOS作为一个功能强大的操作系统,为开发者提供了丰富的开发工具和框架,下面:本文主要介绍macOS如何决定java环境的相关资料,文中通过代码... 目录方法一:使用 which命令方法二:使用 Java_home工具(Apple 官方推荐)那问题来了,