前言

本篇将结合JDK1.6的TreeMap源码,来一起探索红-黑树的奥秘。红黑树是解决二叉搜索树的非平衡问题。

当插入(或者删除)一个新节点时,为了使树保持平衡,必须遵循一定的规则,这个规则就是红-黑规则: 
1) 每个节点不是红色的就是黑色的 
2) 根总是黑色的 
3) 如果节点是红色的,则它的子节点必须是黑色的(反之倒不一定必须为真) 
4) 从跟到叶节点或者空子节点的每条路径,必须包含相同数目的黑色节点

插入一个新节点

红-黑树的插入过程和普通的二叉搜索树基本一致:从跟朝插入点位置走,在每个节点处通过比较节点的关键字相对大小来决定向左走还是向右走。

 public V put(K key, V value) {
Entry<K,V> t = root;
int cmp;
Entry<K,V> parent;
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0) {
t = t.left;
} else if (cmp > 0) {
t = t.right;
} else {
// 注意,return退出方法
return t.setValue(value);
}
} while (t != null);
Entry<K,V> e = new Entry<K,V>(key, value, parent);
if (cmp < 0) {
parent.left = e;
} else {
parent.right = e;
}
fixAfterInsertion(e);
size++;
modCount++;
return null;
}

但是,在红-黑树种,找到插入点更复杂,因为有颜色变换和旋转。fixAfterInsertion()方法就是处理颜色变换和旋转,需重点掌握它是如何保持树的平衡(use rotations and the color rules to maintain the tree’s balance)。

下面的讨论中,使用X、P、G表示关联的节点。X表示一个特殊的节点, P是X的父,G是P的父。

X is a node that has caused a rule violation. (Sometimes X refers to a newly inserted node, and sometimes to the child node when a parent and child have a redred conflict.)

On the way down the tree to find the insertion point, you perform a color flip whenever you find a black node with two red children (a violation of Rule 2). Sometimes the flip causes a red-red conflict (a violation of Rule 3). Call the red child X and the red parent P. The conflict can be fixed with a single rotation or a double rotation, depending on whether X is an outside or inside grandchild of G. Following color flips and rotations, you continue down to the insertion point and insert the new node.

After you’ve inserted the new node X, if P is black, you simply attach the new red node. If P is red, there are two possibilities: X can be an outside or inside grandchild of G. If X is an outside grandchild, you perform one rotation, and if it’s an inside grandchild, you perform two. This restores the tree to a balanced state.

按照上面的解释,讨论可分为3个部分,按复杂程度排列,分别是: 
1) 在下行路途中的颜色变换(Color flips on the way down) 
2) 插入节点之后的旋转(Rotations after the node is inserted) 
3) 在向下路途上的旋转(Rotations on the way down)

在下行路途中的颜色变换(Color flips on the way down)

Here’s the rule: Every time the insertion routine encounters a black node that has two red children, it must change the children to black and the parent to red (unless the parent is the root, which always remains black)

The flip leaves unchanged the number of black nodes on the path from the root on down through P to the leaf or null nodes.

尽管颜色变换不会违背规则4,但是可能会违背规则3。如果P的父是黑色的,则P由黑色变成红色时不会有任何问题,但是,如果P的父是红色的,那么在P的颜色变化之后,就有两个红色节点相连接了。这个问题需要在继续向下沿着路径插入新节点之前解决,可以通过旋转修正这个问题,下文将会看到。

插入节点之后的旋转(Rotations after the node is inserted)

新节点在插入之前,树是符合红-黑规则,在插入新节点之后,树就不平衡了,此时需要通过旋转来调整树的平衡,使之重新符合红-黑规则。

可能性1:P是黑色的,就什么事情也不用做。插入即可。

可能性2:P是红色,X是G的一个外侧子孙节点,则需要一次旋转和一些颜色的变化。 
以插入50,25,75,12,6为例,注意节点6是一个外侧子孙节点,它和它的父节点都是红色。

在这个例子中,X是一个外侧子孙节点而且是左子节点,X是外侧子孙节点且为右子节点,是一种与此对称的情况。通过用50,25,75,87,93创建树,同理再画一画图,这里就省略了。

可能性3:P是红色,X是G的一个内侧子孙节点,则需要两次旋转和一些颜色的改变。 
以插入50,25,75,12,18为例,注意节点18是一个内侧子孙节点,它和它的父节点都是红色。

在向下路途上的旋转(Rotations on the way down)

在插入新节点之前,实际上树已经违背了红-黑规则,所以需要插入新节点之前做调整。所以我们本次讨论的主题是“在向下路途准备插入新节点时,上面先进行调整,使上面成为标准的红黑树后,再进行新节点插入”。

外侧子孙节点

以插入50,25,75,12,37,6,18,3为例,例子中违背规则的节点是一个外侧子孙节点。

内侧子孙节点

以插入50,25,75,12,37,31,43为例,例子中违背规则的节点是一个内侧子孙节点。

红-黑树的效率

和一般的二叉搜索树类似,红-黑树的查找、插入和删除的时间复杂度为O(log2N)。

红-黑树的查找时间和普通的二叉搜索树的查找时间应该几乎完全一样。因为在查找过程中并没用到红-黑特征。额外的开销只是每个节点的存储空间都稍微增加了一点,来存储红黑颜色(一个boolean变量)。

final Entry<K, V> getEntry(Object key) {
Comparable <? super K > k = (Comparable <? super K > ) key;
Entry<K, V> p = root;
while (p != null) {
int cmp = k.compareTo(p.key);
if (cmp < 0) {
p = p.left;
} else if (cmp > 0) {
p = p.right;
} else {
return p;
}
}
return null;
}

插入和删除的时间要增加一个常数因子,因为不得不在下行的路径上和插入点执行颜色变换和旋转。平均起来一次插入大约需要一次旋转。

因为在大多数应用中,查找的次数比插入和删除的次数多,所以应用红-黑树取代普通的二叉搜索树总体上不会增加太多的时间开销。

参考资料

  1. eclipse如何debug调试jdk源码
  2. 浅谈算法和数据结构: 九 平衡查找树之红黑树

结合java.util.TreeMap源码理解红黑树的更多相关文章

  1. Java数据结构和算法 - TreeMap源码理解红黑树

    前言 本篇将结合JDK1.6的TreeMap源码,来一起探索红-黑树的奥秘.红黑树是解决二叉搜索树的非平衡问题. 当插入(或者删除)一个新节点时,为了使树保持平衡,必须遵循一定的规则,这个规则就是红- ...

  2. Java - TreeMap源码解析 + 红黑树

    Java提高篇(二七)-----TreeMap TreeMap的实现是红黑树算法的实现,所以要了解TreeMap就必须对红黑树有一定的了解,其实这篇博文的名字叫做:根据红黑树的算法来分析TreeMap ...

  3. java.util.TreeMap源码分析

    TreeMap的实现基于红黑树,排列的顺序根据key的大小,或者在创建时提供的比较器,取决于使用哪个构造器. 对于,containsKey,get,put,remove操作,保证时间复杂度为log(n ...

  4. Java集合——TreeMap源码详解

    )TreeMap 是一个有序的key-value集合,它是通过红黑树实现的.因为红黑树是平衡的二叉搜索树,所以其put(包含update操作).get.remove的时间复杂度都为log(n). (2 ...

  5. jdk之java.lang.Integer源码理解

    基本数据类型的包装类java.lang.Integer是我们频繁使用的一个系统类,那么通过一个示例反应出的几个问题来深入理解一下此类的源码. 需求:实现Integer类型的两个数值交换. packag ...

  6. jdk源码分析红黑树——插入篇

    红黑树是自平衡的排序树,自平衡的优点是减少遍历的节点,所以效率会高.如果是非平衡的二叉树,当顺序或逆序插入的时候,查找动作很可能会遍历n个节点 红黑树的规则很容易理解,但是维护这个规则难. 一.规则 ...

  7. java.util.HashSet, java.util.LinkedHashMap, java.util.IdentityHashMap 源码阅读 (JDK 1.8)

    一.java.util.HashSet 1.1 HashSet集成结构 1.2 java.util.HashSet属性 private transient HashMap<E,Object> ...

  8. java.util.HashSet, java.util.LinkedHashMap, java.util.IdentityHashMap 源码阅读 (JDK 1.8.0_111)

    一.java.util.HashSet 1.1 HashSet集成结构 1.2 java.util.HashSet属性 private transient HashMap<E,Object> ...

  9. java.util.HashMap源码分析

    在java jdk8中对HashMap的源码进行了优化,在jdk7中,HashMap处理“碰撞”的时候,都是采用链表来存储,当碰撞的结点很多时,查询时间是O(n). 在jdk8中,HashMap处理“ ...

随机推荐

  1. c# 【MVC】WebApi设置返回Json

    public static HttpResponseMessage toJson(Object obj) { String str; if (obj is String || obj is Char) ...

  2. python数据结构之树和二叉树(先序遍历、中序遍历和后序遍历)

    python数据结构之树和二叉树(先序遍历.中序遍历和后序遍历) 树 树是\(n\)(\(n\ge 0\))个结点的有限集.在任意一棵非空树中,有且只有一个根结点. 二叉树是有限个元素的集合,该集合或 ...

  3. Visual Studio 自定义项目模板

    经常我们需要新建一个项目,然后新建我们的View文件夹,ViewModel文件夹,Model文件夹,还有把我们的ViewModelBase放入我们的VIewModel,如果还用框架,还需要加上好多. ...

  4. win10 uwp 上传Nuget 让别人用我们的库

    Nuget 我们的开发经常使用别人的dll,那么我们需要每次都从网上下载,然后复制到我们的项目, 而不知道我们的dll是否安全? 当我们的库更新的时候,我们又需要从网上搜索,这样不好,于是我们就用Nu ...

  5. 【网络爬虫入门05】分布式文件存储数据库MongoDB的基本操作与爬虫应用

    [网络爬虫入门05]分布式文件存储数据库MongoDB的基本操作与爬虫应用 广东职业技术学院  欧浩源 1.引言 网络爬虫往往需要将大量的数据存储到数据库中,常用的有MySQL.MongoDB和Red ...

  6. (原创)(三)机器学习笔记之Scikit Learn的线性回归模型初探

    一.Scikit Learn中使用estimator三部曲 1. 构造estimator 2. 训练模型:fit 3. 利用模型进行预测:predict 二.模型评价 模型训练好后,度量模型拟合效果的 ...

  7. Msys2配置总结

    一.MSYS2的MirrorList配置 1.修改msys2安装目录下的/etc/pacman.d文件夹里面的3个mirrorlist.*文件 [mirrorlist.mingw32] #中国科学技术 ...

  8. python 模块的概念介绍

    模块 模块:本质就是一个.py文件分为三部分:内置模块.第三方模块,自定义模块 模块: 顶层文件 python模块python模块可以将代码量较大的程序分割成多个有组织的.彼此独立但又能互相交互的代码 ...

  9. 脱壳第三讲,UPX压缩壳,以及补充壳知识

    脱壳第三讲,UPX压缩壳,以及补充壳知识 一丶什么是压缩壳.以及壳的原理 在理解什么是压缩壳的时候,我们先了解一下什么是壳 1.什么是壳 壳可以简单理解为就是在自己的PE文件中包含了代码.而有不影响我 ...

  10. 读书笔记-你不知道的JS上-词法作用域

    JS引擎 编译与执行 Javascript引擎会在词法分析和代码生成阶段对运行性能进行优化,包含对冗余元素进行优化(例如对语句在不影响结果的情况下进行重新组合). 对于Javascript来说,大部分 ...