红黑树遵循的条件:

1.根节点为黑色。

2.外部节点(叶子节点)为黑色。

3.红色节点的孩子节点为黑色。(由此,红色节点的父节点也必为黑色)

4.从根节点到任一外部节点的路径上,黑节点的数量相同。

节点插入过程

 在红黑树中插入节点,首先调用查找接口,并在查找终止位置创建节点,并将节点染成红色。

 当新插入节点的父节点为红色时,不满足红黑树的条件,产生‘双红’现象。此时,需要对树的拓扑结构和节点颜色进行调节。

双黑修正过程

 以JDK TreeMap.class 中定义的红黑树类中插入修正函数为例,函数定义如下:

 在这里约定插入节点为x,插入节点的父节点为p,祖父节点为g,曾祖父节点为gg,叔叔节点为u。双红修正根据插入节点周围的相关节点的不同情况,进行相应调整,分为以下几种情况:

修正方法:

1.对于叔叔节点为黑色的情况[UB 1-4]

需要进行1至2次旋转(相当于进行3-4重构)。并对节点进行重新染色,使之满足红黑树约束条件,如下图所示。修正后该局部的最高节点为黑色,所以双红现象不会向上传播,因此,经过本步骤修正后即可使红黑树合法。

2.对于叔叔节点为红色的情况[UR 1-4]

无需要旋转,而仅需对插入节点及相关节点进行重新染色,使之满足红黑树约束条件,如下图所示。修正后该局部的最高节点变为红色,若曾祖父节点gg为红,则会产生新的双红现象,并可能依次向上传播,因此,需递归调用修正函数,继续向上修正,直到整个树合法。

源码剖析

源代码路径/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/rt.jar!/java/util/TreeMap.class (笔者使用的linux上的路径,具体路径根据安装位置会有所不同);

    private void fixAfterInsertion(TreeMap.Entry<K, V> var1) {
var1.color = false; //节点颜色,true:黑色 false:红色 while(var1 != null && var1 != this.root && !var1.parent.color) {
//节点非空,非根节点,且父节点为红色 ==>>需要继续向上进行双红修正 TreeMap.Entry var2;
if (parentOf(var1) == leftOf(parentOf(parentOf(var1)))) {
//若插入节点的父节点p为祖父节点g的左孩子[UB-1,2][UR-1,2] var2 = rightOf(parentOf(parentOf(var1))); //插入节点的叔叔节点U
if (!colorOf(var2)) { //叔叔节点为红色[UR-1,2]
setColor(parentOf(var1), true); //----重新染色----//
setColor(var2, true); //----重新染色----//
setColor(parentOf(parentOf(var1)), false); //----重新染色----//
var1 = parentOf(parentOf(var1)); //节点上升,准备向上修正
} else { //叔叔节点为黑色[UB-1,2]
if (var1 == rightOf(parentOf(var1))) { //插入节点x为右孩子 [UB-2]
var1 = parentOf(var1);
this.rotateLeft(var1); //先左旋
}
//插入节点x为右孩子 [UB-1]
setColor(parentOf(var1), true); //----重新染色----//
setColor(parentOf(parentOf(var1)), false); //----重新染色----//
this.rotateRight(parentOf(parentOf(var1))); //后右旋
}
} else {
//若插入节点的父节点p为祖父节点g的右孩子[UB-3,4][UR-3,4] var2 = leftOf(parentOf(parentOf(var1))); //取得叔叔节点
if (!colorOf(var2)) { //叔叔节点为红色[UR-3,4]
setColor(parentOf(var1), true); //----重新染色----//
setColor(var2, true); //----重新染色----//
setColor(parentOf(parentOf(var1)), false); //----重新染色----//
var1 = parentOf(parentOf(var1)); //节点上升,准备向上修正
} else { //叔叔节点为黑色[UB-3,4]
if (var1 == leftOf(parentOf(var1))) { //插入节点x为左孩子[UB-3]
var1 = parentOf(var1); //
this.rotateRight(var1); //先右旋
}
//插入节点x为右孩子[UB-4]
setColor(parentOf(var1), true); //----重新染色----//
setColor(parentOf(parentOf(var1)), false); //----重新染色----//
this.rotateLeft(parentOf(parentOf(var1))); //左旋
}
}
} this.root.color = true; //为避免将根节点染红,总是进行修正
}

 当插入节点的叔叔节点为红色时[UR],需要将祖父节点g染红,从而可能导致双红现象向上传播(当gg为红时)。此时,需要递归调用双红修正函数,继续向上修正,直至消除树中双红现象。

 在以上JDK代码中,并为出现函数递归调用,而是将递归调用更改为等价的迭代(while循环内)。在处理完叔叔节点为红的情况时,先进行染色,然后将当前节点x更新为其祖父节点g。在while循环内,持续进行修正。每循环一次,当将节点都会上升两层,直到到达根节点,或者gg为黑,修正即完成。

 当x节点的祖父节点g为根节点时,而叔叔节点为红色时,修正函数会将根节点g染红,而违反红黑树规则1,此时,只需将根节点g染黑即可使红黑树恢复合法(函数最后一行),这也是唯一增加树的黑高度的情况。

笔者在阅读代码时,还产生一些疑惑:在[UB-2,3]两种情况的处理过程中,会更新当前节点x,如下所示。

[UB-2]

                    if (var1 == rightOf(parentOf(var1))) {     //插入节点x为右孩子 [UB-2]
var1 = parentOf(var1);
this.rotateLeft(var1); //先左旋
}

[UB-3]

                    if (var1 == leftOf(parentOf(var1))) {      //插入节点x为左孩子[UB-3]
var1 = parentOf(var1); //
this.rotateRight(var1); //先右旋
}

可以看到在旋转前,当前节点x会被替换为其父节点p,然后在对其进行旋转。来看一下旋转函数的定义:

    private void rotateLeft(TreeMap.Entry<K, V> var1) {
if (var1 != null) {
TreeMap.Entry var2 = var1.right; //[1]
var1.right = var2.left; //[2]
if (var2.left != null) {
var2.left.parent = var1; //[3]
} var2.parent = var1.parent; //[4]
if (var1.parent == null) {
this.root = var2; //[5]
} else if (var1.parent.left == var1) {
var1.parent.left = var2; //[6]
} else {
var1.parent.right = var2; //[6']
} var2.left = var1; //[7]
var1.parent = var2; //[8]
} }

函数中各步骤用图表达为:



 设x为当前节点x_old,其在fixAfterInsertion中被上升到父节点x=p,对x调用rotateLeft后,x的高度将降低一层,与原来的旧值x_old处于同一层次。这样在后续进行右旋或左旋时,即可对不同层次进行正确的染色。

 以上图为例,若节点v1p为根节点,则节点r的原深度为3,在fixAfterInsertion中被上升到父节点r=p后,r的深度为2,在对r调用rotateLeft后,r的深度恢复为3,此后再对r调用parentOf后,即可得到深度为2的父节点,对r调用parentOf-parentOf后,得到深度为1的祖父节点,从而确保不同层次的节点被正确染色。右旋的情况左旋相类似。

 由此可以得出结论:在对节点x进行旋转前,先将其指向父节点p,然后实施旋转,此后进行的染色即可和只需要一次旋转的操作保持一致性,使代码更加紧凑。

红黑树插入操作---以JDK 源码为例的更多相关文章

  1. 红黑树的删除操作---以JDK源码为例

    删除操作需要处理的情况: 1.删除的是红色节点,则删除节点并不影响红黑树的树高,无需处理. 2.删除的是黑色节点,则删除后,删除节点所在子树的黑高BH将减少1,需要进行调整. 节点标记: 正在处理的节 ...

  2. JDK源码那些事儿之并发ConcurrentHashMap下篇

    上一篇文章已经就ConcurrentHashMap进行了部分说明,介绍了其中涉及的常量和变量的含义,有些部分需要结合方法源码来理解,今天这篇文章就继续讲解并发ConcurrentHashMap 前言 ...

  3. JDK源码那些事儿之红黑树基础下篇

    说到HashMap,就一定要说到红黑树,红黑树作为一种自平衡二叉查找树,是一种用途较广的数据结构,在jdk1.8中使用红黑树提升HashMap的性能,今天就来说一说红黑树,上一讲已经给出插入平衡的调整 ...

  4. JDK源码那些事儿之红黑树基础上篇

    说到HashMap,就一定要说到红黑树,红黑树作为一种自平衡二叉查找树,是一种用途较广的数据结构,在jdk1.8中使用红黑树提升HashMap的性能,今天就来说一说红黑树. 前言 限于篇幅,本文只对红 ...

  5. 【java基础之jdk源码】集合类

    最近在整理JAVA 基础知识,从jdk源码入手,今天就jdk中 java.util包下集合类进行理解 先看图 从类图结构可以了解 java.util包下的2个大类: 1.Collecton:可以理解为 ...

  6. JDK源码及其他框架源码解析随笔地址导航

    置顶一篇文章,主要是整理一下写过的JDK中各个类的源码及其他框架源码解析的文章,方便自己随时阅读也方便网友朋友们阅读与指正 基础篇 从为什么String=String谈到StringBuilder和S ...

  7. JDK源码分析之hashmap就这么简单理解

    一.HashMap概述 HashMap是基于哈希表的Map接口实现,此实现提供所有可选的映射操作,并允许使用null值和null键.HashMap与HashTable的作用大致相同,但是它不是线程安全 ...

  8. 【JDK】JDK源码分析-LinkedHashMap

    概述 前文「JDK源码分析-HashMap(1)」分析了 HashMap 主要方法的实现原理(其他问题以后分析),本文分析下 LinkedHashMap. 先看一下 LinkedHashMap 的类继 ...

  9. 【JDK】JDK源码分析-HashMap(1)

    概述 HashMap 是 Java 开发中最常用的容器类之一,也是面试的常客.它其实就是前文「数据结构与算法笔记(二)」中「散列表」的实现,处理散列冲突用的是“链表法”,并且在 JDK 1.8 做了优 ...

随机推荐

  1. PAT Advanced 1065 A+B and C (64bit) (20 分)(关于g++和clang++修改后能使用)

    Given three integers A, B and C in [−], you are supposed to tell whether A+B>C. Input Specificati ...

  2. 完整的node脚手架搭建服务

    使用脚手架来搭建node服务,使用到了express架构,不熟悉的可以看下express官方文档:http://www.expressjs.com.cn/ 使用express直接生成服务的文档结构目录 ...

  3. vim 修改复制过来的代码缩进

    命令模式下 :1,9<  //1至9行回退一个tab :1,9> //1至9行缩进一个tab 让不可打印字符心事出来::set list

  4. fs.mkdir

    fs.mkdir(path[, mode], callback) 要求父目录必须存在 let fs = require('fs'); fs.mkdir('./c/b/a', res=>{ // ...

  5. Word快捷选取

    在word中,你知道鼠标单击选中一个词,双击选中一行,三击选中一个段落吗?

  6. IO操作基本步骤

    package com.study02; import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundE ...

  7. 关于Ubuntu 14.04 安装Oracle 11gR2安装步骤(从开始到放弃--最终使用docker获取)

    最近在复习Oracle的相关内容,好准备在下一份工作中能够熟练一些,所以准备在自己虚拟机中安装Oracle的11gR2版本,主要参考内容为: https://blog.csdn.net/qq_4025 ...

  8. CSS盒子模型与怪异盒模型

             盒子模型(Box Modle)可以用来对元素进行布局,包括内边距,边框,外边距,和实际内容这几个部分. 盒子模型分为两种 第一种是W3c标准的盒子模型(标准盒模型) .第二种IE标准 ...

  9. 【noip2016提高组day2T3】【愤怒的小鸟】状压dp转移时的集合包含

    (上不了p站我要死了,图来自百度,侵权度娘背锅) 调死我了... 标题就说明了,死在了集合包含上.因为这道题与其他的状压题不同,其他的题基本上都是要求集合不重合,而这道题完全是可以的. 废话不多说,先 ...

  10. [spring cloud feign] [bug] 使用对象传输get请求参数

    前言 最近在研究 srping cloud feign ,遇到了一个问题,就是当 get 请求 的参数使用对象接收时,就会进入熔断返回.经过百度,发现网上大部分的解决方案都是将请求参数封装到Reque ...