深读源码-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

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听