一、前言


0tnv1e.png

为啥要学红黑树吖?

因为笔者最近在赶项目的时候,不忘抽出时间来复习 Java 基础知识,现在准备看集合的源码啦啦。听闻,HashMapjdk 1.8 的时候,底层的数据结构发生了变化,变成了数组+链表+红黑树。很好,没了解过红黑树,所以就趁今天闲暇学习一下啦

二、什么是红黑树?

2.1 有啥用处?

红黑树从本质上来说就是一颗二叉查找树,但是在二叉树的基础上增加了着色相关的性质,使得红黑树可以保证相对平衡,从而保证红黑树的增删改查的时间复杂度最坏也能达到 O(log N)

2.2 红黑树的六条性质你知道吗?

  1. 每个节点要么是黑的,要么是红的
  2. 根节点是黑的
  3. 叶节点是黑的
  4. 如果一个节点是红的,他的两个儿子节点都是黑的
  5. 对于任一节点而言,其到叶节点树尾端NIL指针的每一条路径都包含相同数目的黑节点。这其实就是黑高啦!
  6. 新插入的节点必须是红色噢!

0tVeC4.png

2.3 插入操作

首先,先看这个图吧,这就是全部的插入操作后,平衡的方法啦!其实我还是喜欢用笔画 hhh


0tZCJe.png

红黑树的概念理解起来较为复杂,我们以一个简单的示例,看看如何构造一棵红黑树。

现有数组int[] a = {1, 10, 9, 2, 3, 8, 7, 4, 5, 6};我们要将其变为一棵红黑树。

首先插入1,此时树是空的,1就是根结点,根结点是黑色的:

首先插入 1,此时树是空的,1 就是根结点,根结点是黑色的:


img

插入 1

然后插入元素 10,此时依然符合规则,结果如下:


img

插入 10

当插入元素 9 时,这时是需要调整的第一种情况,结果如下:


img

插入 9

红黑树规则 4 中强调不能有两个相邻的红色结点,所以此时我们需要对其进行调整。调整的原则有多个相关因素,这里的情况是,父结点 10 是其祖父结点 1(父结点的父结点)的右孩子,当前结点 9 是其父结点 10 的左孩子,且没有叔叔结点(父结点的兄弟结点),此时需要进行两次旋转,第一次,以父结点 10 右旋:


img

右旋

然后将父结点**(此时是 9)**染为黑色,祖父结点 1 染为红色,如下所示:


img

染色

然后以祖父结点 1 左旋:


img

左旋

下一步,插入元素 2,结果如下:


img

插入 2

此时情况与上一步类似,区别在于父结点 1 是祖父结点 9 的左孩子,当前结点 2 是父结点的右孩子,且叔叔结点 10 是红色的。这时需要先将叔叔结点 10 染为黑色,再进行下一步操作,具体做法是将父结点 1 和叔叔结点 10 染为黑色,祖父结点 9 染为红色,如下所示:


img

染色

由于结点 9 是根节点,必须为黑色,将它染为黑色即可:


img

染色

下一步,插入元素 3,如下所示:


img

插入 3

这和我们之前插入元素 10 的情况一模一样,需要将父结点 2 染为黑色,祖父结点 1 染为红色,如下所示:


img

染色

然后左旋:


img

左旋

下一步,插入元素 8,结果如下:


img

插入 8

此时和插入元素 2 有些类似,区别在于父结点 3 是右孩子,当前结点 8 也是右孩子,这时也需要先将叔叔结点 1 染为黑色,具体操作是先将 13 染为黑色,再将祖父结点 2 染为红色,如下所示:


img

染色

此时树已经平衡了,不需要再进行其他操作了,现在插入元素 7,如下所示:


img

插入 7

这时和之前插入元素 9 时一模一样了,先将 78 右旋,如下所示:


img

右旋

然后将 7 染为黑色,3 染为红色,再进行左旋,结果如下:


img

左旋

下一步要插入的元素是 4,结果如下:


img

插入 4

这里和插入元素 2 是类似的,先将 38 染为黑色,7 染为红色,如下所示:


img

染色

但此时 27 相邻且颜色均为红色,我们需要对它们继续进行调整。这时情况变为了父结点 2 为红色,叔叔结点 10 为黑色,且 2 为左孩子,7 为右孩子,这时需要以 2 左旋。这时左旋与之前不同的地方在于结点 7 旋转完成后将有三个孩子,结果类似于下图:


img

错误示意图

这种情况处理起来也很简单,只需要把 7 原来的左孩子 3,变成 2 的右孩子即可,结果如下:


img

调整

然后再把 2 的父结点 7 染为黑色,祖父结点 9 染为红色。结果如下所示:


img

染色

此时又需要右旋了,我们要以 9 右旋,右旋完成后 7 又有三个孩子,这种情况和上述是对称的,我们把 7 原有的右孩子 8,变成 9 的左孩子即可,如下所示:


img

右旋

下一个要插入的元素是 5,插入后如下所示:


img

插入 5

有了上述一些操作,处理 5 变得十分简单,将 3 染为红色,4 染为黑色,然后左旋,结果如下所示:


img

左旋

最后插入元素 6,如下所示:


img

插入 6

又是叔叔结点 3 为红色的情况,这种情况我们处理过多次了,首先将 35 染为黑色,4 染为红色,结果如下:


img

染色

此时问题向上传递到了元素 4,我们看 2479 的颜色和位置关系,这种情况我们也处理过,先将 29 染为黑色,7 染为红色,结果如下:


img

染色

最后 7 是根结点,染为黑色即可,最终结果如下所示:


img

2.4 删除操作

删除的规则如下:


0tmMKs.png

要从一棵红黑树中删除一个元素,主要分为三种情况。

情况 1:待删除元素没有孩子

没有孩子指的是没有值不为 NIL 的孩子。这种情况下,如果删除的元素是红色的,可以直接删除,如果删除的元素是黑色的,就需要进行调整了。

例如我们从下图中删除元素 1:


img

红黑树

删除元素 1 后,2 的左孩子为 NIL,这条支路上的黑色结点数就比其他支路少了,所以需要进行调整。

这时,我们的关注点从叔叔结点转到兄弟结点,也就是结点 4,此时 4 是红色的,就把它染为黑色,把父结点 2 染为红色,如下所示:


img

染色

然后以 2 左旋,结果如下:


img

左旋

此时兄弟结点为 3,且它没有红色的孩子,这时只需要把它染为红色,父结点 2 染为黑色即可。结果如下所示:


img

调整完毕

情况 2:待删除元素有一个孩子

这应该是删除操作中最简单的一种情况了,根据红黑树的定义,我们可以推测,如果一个元素仅有一个孩子,那么这个元素一定是黑色的,而且其孩子是红色的。

假设我们有一个红色节点,它是树中的某一个节点,且仅有一个孩子,那么根据红色节点不能相邻的条件,它的孩子一定是黑色的,如下所示:


img

红色节点仅一个孩子

但这个子树的黑高却不再平衡了(注意每个节点的叶节点都是一个 NIL 节点),因此红色节点不可能只有一个孩子。

而若是一个黑色节点仅有一个孩子,如果其孩子是黑色的,同样会打破黑高的平衡,所以其孩子只能是红色的,如下所示:


img

黑色节点仅一个孩子

只有这一种情况符合红黑树的定义,这时要删除这个元素,只需要使用其孩子代替它,仅代替值而不代替颜色即可,上图的情况删除完后变为:


img

删除完毕

可以看到,树的黑高并没有发生变化,因此也不需要进行调整。

情况 3:待删除元素有两个孩子

我们在讨论二叉排序树时说过,如果删除一个有两个孩子的元素,可以使用它的前驱或者后继结点代替它。因为它的前驱或者后继结点最多只会有一个孩子,所以这种情况可以转为情况 1 或情况 2 处理。

删除元素最复杂的是情况 1,这主要由其兄弟结点以及兄弟结点的孩子颜色共同决定。这里简要做下总结。

我们以 N 代表当前待删除节点,以 P 代表父结点,以 S 代表兄弟结点,以 SL 代表兄弟结点的左孩子,SR 代表兄弟结点的右孩子,如下所示:


img

图样

根据红黑树定义,这种情况下 S 要么有红色的子结点,要么只有 NIL 结点,以下对 S 有黑色结点的情况均表示 NIL

主要有以下几种:

  1. S 是红色,P 一定是黑色,S 也不会有红色的孩子,如下:

img

红色兄弟结点

此时把 PS 颜色变换,再左旋,如下:


img

左旋

这样变换后,N 支路上的黑色结点并没有增加,所以依然少一个,

  1. P,S 以及 S 的全部孩子都是黑色

无论 S 有几个孩子,或者没有孩子,只要不是红色都是这种情况,此时情况如下:


img

全黑色

我们把 S 染为红色,这样一来,NS 两个支路都少了一个黑色结点,所以可以把问题向父结点转移,通过递归解决。染色后如下:


img

染色

  1. P 为红(S 一定为黑),S 的孩子都为黑

这种情况最为简单,只需要把 P 和 S 颜色交换即可。这样 N 支路多了一个黑色元素,而 S 支路没有减少,所以达到了平衡。


img

交换前


img

交换后

  1. P 任意色,S 为黑,N 是 P 的左孩子,S 的右孩子 SR 为红,S 的左孩子任意

如下所示


img

任意色

此时将 S 改为 P 的颜色,SRP 改为黑色,然后左旋,结果如下:


img

左旋

可以发现,此时 N 支路多了一个黑色结点,而其余支路均没有收到影响,所以调整完毕。

  1. P 任意色,S 为黑,N 是 P 的左孩子,S 的左孩子 SL 为红,S 的右孩子 SR 为黑,如下所示:


    img

    SR 黑色

此时变换 SSL 的颜色,然后右旋,结果如下:


img

右旋

这时,所有分支的黑色结点数均没有改变,但情况 5 转为了情况 4,再进行一次操作即可。

还有一些情况与上述是对称的,我们进行相应的转换即可。

红黑树的操作比较复杂,插入元素可能需要多次变色与旋转,删除也是。这些操作的目的都是为了保证红黑树的结构不被破坏。这些复杂的插入与删除操作希望大家可以亲手尝试一下,以加深理解。

三、结语

红黑树其实一开始看起来有点懵逼懵逼的,但是,其实你看完了全文,然后手动模拟一下插入删除操作,发现也不是想象中的很难啦!

附上笔者学习红黑树做的草稿hhh 如果还是看不明白,推荐一个视频:红黑树原理源码讲解(java),全B站讲解最细致版本,看完月薪最少涨5k!


0tnAy9.png

0tn1QH.png

如果文章对您有一点帮助的话,希望您能点一下赞,您的点赞,是我前进的动力

本文参考链接:

本文使用 mdnice 排版

Java 集合 | 红黑树 | 前置知识的更多相关文章

  1. Java实现红黑树

    转自:http://www.cnblogs.com/skywang12345/p/3624343.html 红黑树的介绍 红黑树(Red-Black Tree,简称R-B Tree),它一种特殊的二叉 ...

  2. 基于Java实现红黑树的基本操作

    首先,在阅读文章之前,我希望读者对二叉树有一定的了解,因为红黑树的本质就是一颗二叉树.所以本篇博客中不在将二叉树的增删查的基本操作了,需要了解的同学可以到我之前写的一篇关于二叉树基本操作的博客:htt ...

  3. java数据结构——红黑树(R-B Tree)

    红黑树相比平衡二叉树(AVL)是一种弱平衡树,且具有以下特性: 1.每个节点非红即黑; 2.根节点是黑的; 3.每个叶节点(叶节点即树尾端NULL指针或NULL节点)都是黑的; 4.如图所示,如果一个 ...

  4. Java数据结构——红黑树

    红黑树介绍红黑树(Red-Black Tree),它一种特殊的二叉查找树.执行查找.插入.删除等操作的时间复杂度为O(logn). 红黑树是特殊的二叉查找树,意味着它满足二叉查找树的特征:任意一个节点 ...

  5. Map集合、散列表、红黑树介绍

    前言 声明,本文用得是jdk1.8 前面已经讲了Collection的总览和剖析List集合: Collection总览 List集合就这么简单[源码剖析] 原本我是打算继续将Collection下的 ...

  6. 红黑树(五)之 Java的实现

    概要 前面分别介绍红黑树的理论知识.红黑树的C语言和C++的实现.本章介绍红黑树的Java实现,若读者对红黑树的理论知识不熟悉,建立先学习红黑树的理论知识,再来学习本章.还是那句老话,红黑树的C/C+ ...

  7. 红黑树 Java实现

    概要 前面分别介绍红黑树的理论知识.红黑树的C语言和C++的实现.本章介绍红黑树的Java实现,若读者对红黑树的理论知识不熟悉,建立先学习红黑树的理论知识,再来学习本章.还是那句老话,红黑树的C/C+ ...

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

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

  9. onJava8学习--java集合

    翻翻博客,写了挺多,也学习过这些知识,翻翻脑子,没找到,再来一遍,整理好方便查阅复习. 本次学习内容来自On Java8java编程思想第五版 ​​​​​​ 集合 泛型和类型安全的集合 基本概念 添加 ...

随机推荐

  1. <a>标签的用法。

    1.创建电子邮件链接: <html> <head> <title>发给朱永成</title> </head> <body> &l ...

  2. springMvc全局异常处理

    本文中只测试了:实现Spring的异常处理接口HandlerExceptionResolver 自定义自己的异常处理器 对已有代码没有入侵性等优点,同时,在异常处理时能获取导致出现异常的对象,有利于提 ...

  3. WEB相关文件的加载顺序

    一. 1.启动一个WEB项目,WEB容器会先去读取它的配置文件web.xml,读取<context-param>和<listener>两个节点. 2.接着,容器创建一个Serv ...

  4. bzoj3275: Number

    最小割...然后推一下可知不能的情况必定为一奇一偶,于是s->奇->偶->t.跑最小割即可. #include<cstdio> #include<cstring&g ...

  5. OOAD(面向对象分析和设计)GRASP之创建者模式(Creator)又称生成器模式学习笔记

    说OOAD是一门玄学,一点都不为过.又或许是因为我之前一直没有很好的建立面向对象的思想,更有可能是因为练得不够多...总之,一直没能很好理解,哪怕把一本叫做<UML和模式应用>的书翻来覆去 ...

  6. Python Django的生产部署方式

    本地化部署的Django有很大的局限性,无法用于生产环境,比如无法抗住多并发,无法长时间的运行,容易造成网页无响应的问题.所以如何将Django部署到真正的生产环境中,让其能够真正的像正常的网页一样工 ...

  7. C# web项目利用docx文档作为模板~为打印专做的解决方案

    还是那句话:十年河东,十年河西,莫欺少年穷. 目前,web端打印技术有很多,有收费的专业web打印控件,大家可以参考我的上一篇博客.当然,很多公司不愿意出钱,那么今天咱们就探讨下怎么做免费的打印. w ...

  8. GitLab上传项目到新的分支

    多人协同开发,GitLab上的group仓库里的master分支作为开发分支(最终从dev提交的代码),dev分支作为每个人的代码测试后合并的分支,每个人需要定期merge request自己的分支到 ...

  9. idea Error:(1, 10) java: 需要class, interface或enum, 未结束的字符串文字,Error:(55, 136) java: 非法字符: \65533

    1.未结束的字符串文字,Error:(55, 136) java: 非法字符: \65533  这些乱七吧八遭的错误如果很多的话 , 尝试 重新修改下生成目录 修改下语言等级 上述方法都不行 ,还报错 ...

  10. 转。。原理同样支持 delphi

    我用C#导出excel 带图片,用office2003 正常,但换成office 2007 时,我指定多个单元格分别插入图片,这个图片不在此单元格内,这些图片全都集中在一起,在一个位置上.很奇怪,看起 ...