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

前言

限于篇幅,本文只对红黑树的基础进行说明,暂不涉及源码部分,大部分摘抄自维基百科,这里也贴出对应链接:

维基百科(中文):https://zh.wikipedia.org/wiki/%E7%BA%A2%E9%BB%91%E6%A0%91

维基百科:https://en.wikipedia.org/wiki/Red%E2%80%93black_tree

笔者这里会根据维基百科的讲解做些说明,方便初学者理解。

删除平衡

在正式进入红黑树的删除说明之前,想下二叉搜索树中的删除是如何做的?

参照二叉搜索树的删除调整原理:

如果需要删除的节点有两个儿子,那么问题可以被转化成删除另一个只有一个儿子的节点的问题(为了表述方便,这里所指的儿子,为非叶子节点的儿子)。对于二叉查找树,在删除带有两个非叶子儿子的节点的时候,我们要么找到它左子树中的最大元素、要么找到它右子树中的最小元素,并把它的值转移到要删除的节点中(如在这里所展示的那样)。我们接着删除我们从中复制出值的那个节点,它必定有少于两个非叶子的儿子。因为只是复制了一个值(没有复制颜色),不违反任何性质,这就把问题简化为如何删除最多有一个儿子的节点的问题。它不关心这个节点是最初要删除的节点还是我们从中复制出值的那个节点。

那么红黑树中会出现哪些情况呢?

  • 删除节点的左右子树都非空
  • 删除节点的左子树为空,右子树非空
  • 删除节点的右子树为空,左子树非空
  • 删除节点的左右子树都为空

对于第一种情况,我们可以找到删除节点的后继节点,将值替换,然后删除后继节点,这样保证了该树仍然是一棵二叉树,但是在删除后继节点时可能破坏了红黑树的特性,故需要进行调整。强调一下,红黑树中的叶子节点指的都是NIL节点。

这样来看,被删除的节点一定有一个右子树,可能为NIL也可能为非空子树,接下来就具体看看情况。

1.如果删除节点E为红色,则E子节点F则必为黑色(红黑树特性),这种情况只有在E的两个节点都为叶子节点时才会发生。故删除之后红黑树平衡不用调整。可以自行画图验证:

  • 删除节点E有两个非叶子节点,这不可能,因为E已经是后继节点。
  • E有一个非叶子节点(右子节点),则这个非叶子节点F为黑色,通过E的两条黑色路径不同,不满足特性5

2.如果删除节点E为黑色,F为红色,这种情况下,F节点有两个叶子节点(需保证红黑树特性,黑色路径需保持一致)则F放置到E处时只需要变色就可以使得红黑树平衡

3.如果删除节点E为黑色,F也为黑色,这种情况只有在E的两个节点都为叶子节点时才会发生。参考上边情况1的验证。这里删除了一个黑色节点,红黑树平衡被破坏(黑色路径不同了),需要进行调整

针对3这里就又会有以下几种情况:

N为删除的位置节点,现在被删除的节点的子节点取代(这里子节点对应上边的F,即删除后,N的位置为叶子节点),N为黑色,P为N的父母,S为P的右子,SL代表S的左子,SR代表S的右子。

S必不为叶子节点,反推下,如果为叶子节点,在未删除之前P的两边黑色路径就不一致了(未删除之前是P->E->F这种,自行画图理解)。注意,下面列举的情况都是在删除E节点后,子节点取代E形成的情况,在此基础上进行红黑树的调整。按照顺序每种情况进行判断处理。注意每种情况处理之后会重新标记,以适应下次情况的对比调整,并且下列过程只以第一次调用时说明,递归调用下列顺序过程时将叶子节点当成一个已经平衡的局部红黑树即可。和之前的插入平衡调整类似,每次都是局部化调整。

第一种情况:如果N为根节点,不需要调整平衡了,原有树只有一个非叶子节点,两个叶子节点,删除了根节点,相当于删除了红黑树。继续第二种情况判断。

第二种情况:如果N(这里是叶子节点NIL)是其父P的左子节点,S为红色,P必为黑色,参照下图,反转P和S的颜色,然后在P处向左旋转,将S转换为N的祖父母。这里N处的黑色路径少一个,还未平衡。N是其父P的右子节点参照相似处理。SL标记为新的S继续以N,P,S这块局部树进行处理。继续第三种情况处理。

第三种情况:如果P,S和S的孩子都是黑色,左图显示了出现的情况,在N替换掉之前的父节点后形成的状态,这里重新绘制S为红色,变为右图,在这个局部中满足平衡红黑树的特性4和5,但是通过P节点的黑色路径相比原有结构减少了1,还需要进行调整,需重新进行平衡。这里重新平衡即从第一种情况再次顺序执行,以P节点进行重新平衡,相当于以P为新的N,黑色路径少1,再次进行平衡调整。不满足当前情况,再继续执行第4种情况处理。

第四种情况:如果S和S的孩子是黑色,但P是红色的。在这种情况下,我们只需交换S和P的颜色。这不会影响通过S的路径上的黑色节点数量,但它确实会在通过N的路径上添加一个黑色节点数,从而弥补这些路径上已删除的黑色节点。将达到红黑树平衡。不满足当前情况,则继续第五种情况的处理。

第五种情况:如果S是黑色,S的左孩子是红色,S的右孩子是黑色,N是其父母的左孩子。在这种情况下,我们在S处右转,这样S的左边孩子就成了S的父母和N的新兄弟。然后我们交换S及其新父母的颜色。所有路径仍然有相同数量的黑色结点,但是P的左子树因为删除一个节点导致黑色路径少1,还未完全平衡。这里进行调整主要是为了满足第六种情况,继续第六种情况的处理。

第六种情况: 如果S是黑色,S的右子是红色,N是其父P的左子。在这种情况下,我们向左旋转P,这样S成为P和S的右子的父亲。然后,我们交换P和S的颜色,并使S的右子节点变黑。比较删除前与N替换调整后的属性,满足4和5,与删除前是一致的。

总结

删除操作相对插入操作之后的平衡要复杂的多,不过按照情况一步步处理也是比较明了的,同样为了方便初学者理解,从上边的过程我们也可以发现,在一次局部平衡调整中,最多进行3次旋转操作,我这里再进行一个流程梳理,帮助各位更好的理解红黑树的删除操作。

到此关于红黑树的基础已经介绍完毕,下一章我将就JDK源码中的TreeNode进行讲解说明,看一看红黑树是如何在源码中实现的。

参考资料:

  1. https://zh.wikipedia.org/wiki/%E7%BA%A2%E9%BB%91%E6%A0%91
  2. https://en.wikipedia.org/wiki/Red%E2%80%93black_tree
  3. https://my.oschina.net/hosee/blog/618828
  4. https://www.cnblogs.com/tongy0/p/5460623.html

JDK源码那些事儿之红黑树基础下篇的更多相关文章

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

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

  2. JDK源码那些事儿之HashMap.TreeNode

    前面几篇文章已经讲解过HashMap内部实现以及红黑树的基础知识,今天这篇文章就讲解之前HashMap中未讲解的红黑树操作部分,如果没了解红黑树,请去阅读前面的两篇文章,能更好的理解本章所讲解的红黑树 ...

  3. JDK源码那些事儿之并发ConcurrentHashMap上篇

    前面已经说明了HashMap以及红黑树的一些基本知识,对JDK8的HashMap也有了一定的了解,本篇就开始看看并发包下的ConcurrentHashMap,说实话,还是比较复杂的,笔者在这里也不会过 ...

  4. JDK源码那些事儿之我眼中的HashMap

    源码部分从HashMap说起是因为笔者看了很多遍这个类的源码部分,同时感觉网上很多都是粗略的介绍,有些可能还不正确,最后只能自己看源码来验证理解,写下这篇文章一方面是为了促使自己能深入,另一方面也是给 ...

  5. JDK源码那些事儿之浅析Thread上篇

    JAVA中多线程的操作对于初学者而言是比较难理解的,其实联想到底层操作系统时我们可能会稍微明白些,对于程序而言最终都是硬件上运行二进制指令,然而,这些又太过底层,今天来看一下JAVA中的线程,浅析JD ...

  6. JDK源码那些事儿之常用的ArrayList

    前面已经讲解集合中的HashMap并且也对其中使用的红黑树结构做了对应的说明,这次就来看下简单一些的另一个集合类,也是日常经常使用到的ArrayList,整体来说,算是比较好理解的集合了,一起来看下 ...

  7. 浅析Java源码之HashMap外传-红黑树Treenode(已鸽)

    (这篇文章暂时鸽了,有点理解不能,点进来的小伙伴可以撤了) 刚开始准备在HashMap中直接把红黑树也过了的,结果发现这个类不是一般的麻烦,所以单独开一篇. 由于红黑树之前完全没接触过,所以这篇博客相 ...

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

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

  9. JDK源码那些事儿之ConcurrentLinkedDeque

    非阻塞队列ConcurrentLinkedQueue我们已经了解过了,既然是Queue,那么是否有其双端队列实现呢?答案是肯定的,今天就继续说一说非阻塞双端队列实现ConcurrentLinkedDe ...

随机推荐

  1. [转帖]分享一份珍藏多年的PG数据库部署架构图

    分享一份珍藏多年的PG数据库部署架构图 记得同事曾经测试过citus https://www.toutiao.com/i6710613553277043213/ 原创 波波说运维 2019-07-11 ...

  2. [转帖]详解Linux系统inode原理--硬链接、软链接、innodb大小和划分等

    详解Linux系统inode原理--硬链接.软链接.innodb大小和划分等 原创 波波说运维 2019-07-17 00:03:00 https://www.toutiao.com/i6713116 ...

  3. hadoop WordCount例子详解。

    [学习笔记] 下载hadoop-2.7.4-src.tar.gz,拷贝hadoop-2.7.4-src.tar.gz中hadoop-mapreduce-project\hadoop-mapreduce ...

  4. wireguard使用方法

    1.翻墙访问网页:https://cryptostorm.is/wireguard.cgi 并下载客户端 2. 选者第二个并打开 3.复制publickey 4.黏贴在第二行并addkey: 5.将获 ...

  5. Python——类和对象(一)

    一.定义类 在面向对象的程序设计中有两种重要概念: 类:可以理解为一个种类,一个模型,是一种抽象的东西. 实例.对象:可以理解为一种具体制作或者存在的东西. 定义类的语法格式如下: class 类名: ...

  6. 2019杭电多校一 A. Blank (dp)

    大意: 长为$n$的数组, 每个位置范围$[0,3]$, $m$个限制$(l,r,x)$表示$[l,r]$内有$x$种数, 求方案数. 维护每个数字最后一次出现位置, 暴力$DP$ 实现时有个技巧是把 ...

  7. Lieges of Legendre CodeForces - 603C (博弈论,SG找规律)

    大意: 给定$n$堆石子, 两人轮流操作, 每次操作两种选择 $(1)$任选非空堆拿走一个石子 $(2)$任选石子数为$2x(x>0)$的一堆, 替换为$k$堆$x$个石子. ($k$给定) 最 ...

  8. 怎样理解new命令的执行过程

    通过new命令生成一个实例对象经历了四个步骤: 1. 创建一个空对象, 作为将要返回的实例; 2. 将空对象的原型指向构造函数的prototype属性; 3. 将空对象赋值给构造函数的this; 4. ...

  9. 安装Docker step by step

    1. 系统要求 centos7以上   使用cat /etc/redhat-release查看系统版本,我的Centos 7.6 centos-extra 仓库 enable,默认是打开的 2.安装d ...

  10. CUDA 笔记

    名词解释 SM :Streaming Multiprocessor    而 Block 大致就是对应到 SM     所有的blocks 按照流水线被送到6个SM中进行计算 在 Compute Ca ...