学过数据结构都知道二叉树的概念,而又有多种比较常见的二叉树类型,比如完全二叉树、满二叉树、二叉搜索树、均衡二叉树、完美二叉树等。

图片来自 Pexels

今天我们要说的红黑树就是就是一棵非严格均衡的二叉树,均衡二叉树又是在二叉搜索树的基础上增加了自动维持平衡的性质,插入、搜索、删除的效率都比较高。红黑树也是实现 TreeMap 存储结构的基石。

二叉搜索树

二叉搜索树又叫二叉查找树、二叉排序树,我们先看一下典型的二叉搜索树,这样的二叉树有何规则特点呢?

二叉搜索树有如下几个特点:

  • 节点的左子树小于节点本身

  • 节点的右子树大于节点本身

  • 左右子树同样为二叉搜索树

下图就是一棵典型的二叉搜索树:

二叉搜索树是均衡二叉树的基础,我们看一下它的搜索步骤如何。我们要从二叉树中找到值为 58 的节点。

第一步:首先查找到根节点,值为 60 的节点。

第二步:比较我们要找的值 58 与该节点的大小。

如果等于,那么恭喜,已经找到;如果小于,继续找左子树;如果大于,那么找右子树。

很明显 58<60,因此我们找到左子树的节点 56,此时我们已经定位到了节点 56。

第三步:按照第二步的规则继续找。

58>56 我们需要继续找右子树,定位到了右子树节点 58,恭喜,此时我们已经找到了。我们经过三步就已经找到了,其实就是我们平时所说的二分查找,这种二叉搜索树好像查找效率很高,但同样它也有缺陷,如下面这样的二叉搜索树。

看到这样的二叉搜索树是否很别扭,典型的大长腿瘸子,但它也是二叉搜索树,如果我们要找值为 50 的节点,基本上和单链表查询没多大区别了,性能将大打折扣。

这个时候我们的均衡二叉树就粉墨登场了,均衡二叉树就是在二叉搜索树的基础上添加了自动维持平衡的性质。

上面的大长腿瘸子二叉搜索树经过自动平衡后,可能就成为了下面这样的二叉树。

经过了自动平衡,再去找值为 50 的节点,查找性能将提升很多。红黑树就是非严格均衡的二叉搜索树。

红黑树规则特点

红黑树具体有哪些规则特点呢?具体如下:

  • 节点分为红色或者黑色。

  • 根节点必为黑色。

  • 叶子节点都为黑色,且为 null。

  • 连接红色节点的两个子节点都为黑色(红黑树不会出现相邻的红色节点)。

  • 从任意节点出发,到其每个叶子节点的路径中包含相同数量的黑色节点。

  • 新加入到红黑树的节点为红色节点。

规则看着好像挺多,没错,因为红黑树也是均衡二叉树,需要具备自动维持平衡的性质,上面的 6 条就是红黑树给出的自动维持平衡所需要具备的规则。

我们看一看一个典型的红黑树到底是什么样儿?

首先解读一下规则,除了字面上看到的意思,还隐藏了哪些意思呢?

①从根节点到叶子节点的最长路径不大于最短路径的 2 倍

怎么样的路径算最短路径?从规则 5 中,我们知道从根节点到每个叶子节点的黑色节点数量是一样的,那么纯由黑色节点组成的路径就是最短路径。

什么样的路径算是最长路径?根据规则 4 和规则 3,若有红色节点,则必然有一个连接的黑色节点,当红色节点和黑色节点数量相同时,就是最长路径,也就是黑色节点(或红色节点)*2。

②为什么说新加入到红黑树中的节点为红色节点

从规则 4 中知道,当前红黑树中从根节点到每个叶子节点的黑色节点数量是一样的,此时假如新的是黑色节点的话,必然破坏规则。

但加入红色节点却不一定,除非其父节点就是红色节点,因此加入红色节点,破坏规则的可能性小一些,下面我们也会举例来说明。

什么情况下,红黑树的结构会被破坏呢?破坏后又怎么维持平衡,维持平衡主要通过两种方式【变色】和【旋转】,【旋转】又分【左旋】和【右旋】,两种方式可相互结合。

下面我们从插入和删除两种场景来举例说明。

红黑树节点插入

当我们插入值为 66 的节点时,红黑树变成了这样:

很明显,这个时候结构依然遵循着上述 6 大规则,无需启动自动平衡机制调整节点平衡状态。

如果再向里面插入值为 51 的节点,这个时候红黑树变成了这样:

很明显现在的结构不遵循规则 4 了,这个时候就需要启动自动平衡机制调整节点平衡状态。

变色

我们可以通过变色的方式,使结构满足红黑树的规则:

  • 首先解决结构不遵循规则 4 这一点(红色节点相连,节点 49-51),需将节点 49 改为黑色。

  • 此时我们发现又违反了规则 5(56-49-51-XX 路径中黑色节点超过了其他路径),那么我们将节点 45 改为红色节点。

  • 哈哈,妹的,又违反了规则 4(红色节点相连,节点 56-45-43),那么我们将节点 56 和节点 43 改为黑色节点。

  • 但是我们发现此时又违反了规则 5(60-56-XX 路径的黑色节点比 60-68-XX 的黑色节点多),因此我们需要调整节点 68 为黑色。

  • 完成!

最终调整完成后的树为:

但并不是什么时候都那么幸运,可以直接通过变色就达成目的,大多数时候还需要通过旋转来解决。

如在下面这棵树的基础上,加入节点 65:

插入节点 65 后进行以下步骤:

这个时候,你会发现对于节点 64 无论是红色节点还是黑色节点,都会违反规则 5,路径中的黑色节点始终无法达成一致,这个时候仅通过【变色】已经无法达成目的。我们需要通过旋转操作,当然【旋转】操作一般还需要搭配【变色】操作。旋转包括【左旋】和【右旋】。

左旋:逆时针旋转两个节点,让一个节点被其右子节点取代,而该节点成为右子节点的左子节点。

左旋操作步骤如下:首先断开节点 PL 与右子节点 G 的关系,同时将其右子节点的引用指向节点 C2;然后断开节点 G 与左子节点 C2 的关系,同时将 G 的左子节点的应用指向节点 PL。

右旋:顺时针旋转两个节点,让一个节点被其左子节点取代,而该节点成为左子节点的右子节点。

右旋操作步骤如下:首先断开节点 G 与左子节点 PL 的关系,同时将其左子节点的引用指向节点 C2;然后断开节点 PL 与右子节点 C2 的关系,同时将 PL 的右子节点的应用指向节点 G。

无法通过变色而进行旋转的场景分为以下四种:

左左节点旋转

这种情况下,父节点和插入的节点都是左节点,如下图(旋转原始图1)这种情况下,我们要插入节点 65。

规则如下:以祖父节点【右旋】,搭配【变色】。

按照规则,步骤如下:

左右节点旋转

这种情况下,父节点是左节点,插入的节点是右节点,在旋转原始图 1 中,我们要插入节点 67。规则如下:先父节点【左旋】,然后祖父节点【右旋】,搭配【变色】。

按照规则,步骤如下:

右左节点旋转

这种情况下,父节点是右节点,插入的节点是左节点,如下图(旋转原始图 2)这种情况,我们要插入节点 68。

规则如下:先父节点【右旋】,然后祖父节点【左旋】,搭配【变色】。

按照规则,步骤如下:

右右节点旋转

这种情况下,父节点和插入的节点都是右节点,在旋转原始图 2 中,我们要插入节点 70。规则如下:以祖父节点【左旋】,搭配【变色】。

按照规则,步骤如下:

红黑树插入总结

红黑树插入总结如下图:

红黑树节点删除

相比较于红黑树的节点插入,删除节点更为复杂,我们从子节点是否为 null 和红色为思考维度来讨论。

子节点至少有一个为 null

当待删除的节点的子节点至少有一个为 null 节点时,删除了该节点后,将其有值的节点取代当前节点即可。

若都为 null,则将当前节点设置为 null,当然如果违反规则了,则按需调整,如【变色】以及【旋转】。

子节点都是非 null 节点

这种情况下,第一步:找到该节点的前驱或者后继。前驱:左子树中值最大的节点(可得出其最多只有一个非 null 子节点,可能都为 null)。后继:右子树中值最小的节点(可得出其最多只有一个非 null 子节点,可能都为 null)。前驱和后继都是值最接近该节点值的节点,类似于该节点.prev=前驱,该节点.next=后继。第二步:将前驱或者后继的值复制到该节点中,然后删掉前驱或者后继。如果删除的是左节点,则将前驱的值复制到该节点中,然后删除前驱;如果删除的是右节点,则将后继的值复制到该节点中,然后删除后继。这相当于是一种“取巧”的方法,我们删除节点的目的是使该节点的值在红黑树上不存在。因此专注于该目的,我们并不关注删除节点时是否真是我们想删除的那个节点,同时我们也不需考虑树结构的变化,因为树的结构本身就会因为自动平衡机制而经常进行调整。前面我们已经说了,我们要删除的实际上是前驱或者后继,因此我们就以前驱为主线来讲解。后继的学习可参考前驱,包括下面几种情况:

①前驱为黑色节点,并且有一个非 null 子节点

分析:因为要删除的是左节点 64,找到该节点的前驱 63;然后用前驱的值 63替换待删除节点的值 64,此时两个节点(待删除节点和前驱)的值都为 63;删除前驱 63,此时成为上图过程中间环节,但我们发现其不符合红黑树规则 4,因此需要进行自动平衡调整。这里直接通过【变色】即可完成。

②前驱为黑色节点,同时子节点都为 null

分析:因为要删除的是左节点 64,找到该节点的前驱 63;然后用前驱的值 63 替换待删除节点的值 64,此时两个节点(待删除节点和前驱)的值都为 63。删除前驱 63,此时成为上图过程中间环节,但我们发现其不符合红黑树规则 5,因此需要进行自动平衡调整。这里直接通过【变色】即可完成。

③前驱为红色节点,同时子节点都为 null

分析:因为要删除的是左节点 64,找到该节点的前驱 63;然后用前驱的值 63替换待删除节点的值 64,此时两个节点(待删除节点和前驱)的值都为 63;删除前驱 63,树的结构并没有打破规则。

红黑树删除总结

红黑树删除的情况比较多,但也就存在以下情况:

  • 删除的是根节点,则直接将根节点置为 null。

  • 待删除节点的左右子节点都为 null,删除时将该节点置为 null。

  • 待删除节点的左右子节点有一个有值,则用有值的节点替换该节点即可。

  • 待删除节点的左右子节点都不为 null,则找前驱或者后继,将前驱或者后继的值复制到该节点中,然后删除前驱或者后继。

  • 节点删除后可能会造成红黑树的不平衡,这时我们需通过【变色】+【旋转】的方式来调整,使之平衡,上面也给出了例子,建议大家多多练习,而不必背下来。

总结

本文主要介绍了红黑树的相关原理,首先红黑树的基础二叉搜索树,我们先简单说了一下二叉搜索树,并且讲了一下搜索的流程。然后就针对红黑树的六大规则特点,红黑树的插入操作,删除操作,都使用了大量的图形来加以说明。技术都是练出来的,有时候很多似是而非的地方,当动笔去写的时候,其实很好理解。红黑树的使用非常广泛,如 TreeMap 和 TreeSet 都是基于红黑树实现的,而 JDK8 中 HashMap 当链表长度大于 8 时也会转化为红黑树。红黑树比较复杂,本人也是还在学习过程中,如果有不对的地方请批评指正,望共同进步谢谢。作者:梁洪简介:网名工匠初心,热爱技术,喜欢钻研与分享,6 年 Java 开发经验,专注于 Java 以及 Spring 生态圈,同时也喜欢研究物联网、大数据、AI 等前沿技术,带过 15 人以下的小团队,做过项目管理,现在是一家软件公司的部门经理。

编辑:陶家龙、孙淑娟

出处:https://www.cnblogs.com/LiaHon/p/11203229.html

征稿:有投稿、寻求报道意向技术人请联络 editor@51cto.com

精彩文章推荐:

傻瓜都能看懂,30张图彻底理解红黑树!为什么MySQL数据库要用B+树存储索引?面试不再慌!跟着老司机吃透Spring Cloud

红黑树与平衡二叉树_图解“红黑树”原理,一看就明白!相关推荐

  1. 红黑树与平衡二叉树_百图详解红黑树,想不理解都难

    之前在公司组内分享了红黑树的工作原理,今天把它整理下发出来,希望能对大家有所帮助,对自己也算是一个知识点的总结. 这篇文章算是我写博客写公众号以来画图最多的一篇文章了,没有之一,我希望尽可能多地用图片 ...

  2. 红黑树与平衡二叉树_百图详解红黑树

    之前在公司组内分享了红黑树的工作原理,今天把它整理下发出来,希望能对大家有所帮助,对自己也算是一个知识点的总结. 这篇文章算是我写博客写公众号以来画图最多的一篇文章了,没有之一,我希望尽可能多地用图片 ...

  3. 为什么红黑树查询快_为什么红黑树的效率比较高

    红黑树属于平衡二叉树.它不严格是因为它不是严格控制左.右子树高度或节点数之差小于等于1,但红黑树高度依然是平均log(n),且最坏情况高度不会超过2log(n). 红黑树(red-black tree ...

  4. 红黑树的删除_从红黑树的本质出发,彻底理解红黑树!

    前言 早上好,我是彤哥. 上一节,我们一起从二叉树.二叉查找树.平衡树.AVL树.2-3树.2-3-4树.B树,一路讲到红黑树,最后得出红黑树的本质:红黑树就是2-3-4树,请看下图: 我们知道2-3 ...

  5. promise用法_图解 Promise 实现原理(四):Promise 静态方法实现

    作者:Morrain 转发链接:https://mp.weixin.qq.com/s/Lp_5BXdpm7G29Z7zT_S-bQ 前言 Promise 是异步编程的一种解决方案,它由社区最早提出和实 ...

  6. 实现同步请求_图解 Promise 实现原理(二)—— Promise 链式调用

    摘要 很多同学在学习 Promise 时,知其然却不知其所以然,对其中的用法理解不了.本系列文章由浅入深逐步实现 Promise,并结合流程图.实例以及动画进行演示,达到深刻理解 Promise 用法 ...

  7. promise用法_图解 Promise 实现原理(一)—— 基础实现

    本文首发于 vivo互联网技术 微信公众号 链接: https://mp.weixin.qq.com/s/UNzYgpnKzmW6bAapYxnXRQ 作者:孔垂亮 很多同学在学习 Promise 时 ...

  8. yarn的组成部分_图解YARN工作原理

    YARN 即MapReduce V2版本.相比MapReduce V1 它有很多优点: 1. 分散了jobTracker 的任务.资源管理任务由资源管理器负责,作业启动.运行和监测任务由分布在集群节点 ...

  9. mysql数据丢失_图解MySQL | 「原理解析」 MySQL使用固定的server_id导致数据丢失

    原创作者:爱可生开源社区 本文我们来看一个场景,两台MySQL实例使用主从复制,当master故障,触发高可用切换,新master上线后,通过备份重建旧master并建立复制后,数据发生丢失. 以下我 ...

最新文章

  1. 华为有了鸿蒙就可以翻身吗,鸿蒙2.0要来了,华为手机能靠此翻身吗?
  2. TSP_遗传算法求解
  3. 技术沙龙直播 | 数据库技术探索及行业应用
  4. 不动产中心考试计算机测试题,2005年全国计算机二级考试VFP笔试模拟题
  5. .net oracle 连接超时_ADO.NET (一) 前言
  6. 特斯拉花式作妖:停售Model S标准续航版 国内官网已下架
  7. UDP socket编程: C++发送 | C#接收
  8. linux命令不断更新
  9. Windows10实用技巧-固定快捷方式到磁贴菜单方式
  10. PredRNN++: Towards A Resolution of the Deep-in-Time Dilemma in Spatiotemporal Predictive Learning
  11. 经济型EtherCAT运动控制器(六):数据储存
  12. IPS,LED,LCD,TFT简介
  13. VS 注释多行与取消多行注释快捷键
  14. 数据科学家的一天,是怎样度过的?
  15. 分享文章:如何提高你的学习能力,也许对你会有用
  16. ChinaSoft 论坛巡礼 | NASAC 青年软件创新奖论坛
  17. word2vec是这样演变到bert的
  18. 地级市固定资产投资和对外经济贸易指标
  19. 动态规划,且学且放弃
  20. java jpa saveall方法优化_JPA批量保存saveAll的问题

热门文章

  1. python中的进制,python中的十进制到十六进制
  2. think php5关联模型,thinkphp5中关联模型的定义与使用方法
  3. python max函数时间复杂度_程序员的思考:用函数还是用复杂表达式
  4. mongodb 良贴
  5. Java笔记(十二) 文件基础技术
  6. UWP开发随笔——UWP新控件!AutoSuggestBox!
  7. 软件测试 第三次作业
  8. C语言之数组为参数传递表示指针(三十七)
  9. Android10加入APEX目的
  10. Ubuntu之重新安装软件