图解红黑树的前世今生

本文深入探讨了红黑树的起源,从2-3-4树的概念出发,详细解释了2-3-4树到红黑树的转化过程。红黑树是2-3-4树的二进制表示,通过节点颜色来模拟2-3-4树的不同节点类型。文章介绍了左倾红黑树的插入和删除规则,包括旋转和染色的原理,帮助读者理解红黑树如何保持平衡。此外,还涵盖了2-3树的插入和删除操作,为理解红黑树提供了基础。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

红黑树是面试中一个很经典也很有难度的知识点,面试官比较喜欢问这方面的问题,可能有的人会觉得学习这个知识点太难,也觉得问得少没必要掌握,我可以明确告诉大家,当你去面试时HashMap是常问的问题,而问到HashMap就必然会和红黑树有关联,而HashMap的底层实现就是红黑树+链表。网上很多博客都不能够清晰完整的描述出红黑树的整个体系,对于红黑平衡调整的细节部分也没有很详尽的介绍,因此给学习带来了较大的困难,只要你足够认真,读完这篇文章能够真正的将红黑树变成自己的知识点。

2-3-4树

红黑树的本质其实也是对概念模型:2-3-4树的一种实现,因此我们先来关注2-3-4树。

2-3-4树是一种阶为4的B树(全名BalanceTree),它是一种自平衡的数据结构,这种结构主要用来做查找,关于B树(平衡多路查找树)的定义,网上已经有很多介绍,它最重要的特性在于平衡,这使得我们能够在最坏情况下也保持O(LogN)的时间复杂度实现查找。

首先2-3-4树为什么要叫做2-3-4树?因为这棵树除了叶子节点,所有的节点都只有2个子节点或3个子节点或4个子节点,具有如下性质:

  • 任一节点只能是1个或2个或3个key,对应的子节点为2个子节点或3个子节点或4个子节点。
  • 所有叶子节点到根节点的长度一致。
  • 每个节点的key从左到右保持了从小到大的顺序,两个key之间的子树中所有的key一定大于它的父节点的左key,小于父节点的右key,对于3个key的节点,两两key之间也是如此。
如下图所示:

在这里插入图片描述

2-3-4树到红黑树的转化

红黑树是对概念模型2-3-4树的一种实现,由于直接进行不同节点间的转化会造成较大的开销,所以选择以二叉树为基础,在二叉树的属性中加入一个颜色属性来表示2-3-4树中不同的节点。

由于直接进行不同节点间的转化会造成较大的开销,所以选择以二叉树为基础,在二叉树的属性中加入一个颜色属性来表示2-3-4树中不同的节点。

2-3-4树中的2节点对应着红黑树中的黑色节点,而2-3-4树中的非2节点是以红节点+黑节点的方式存在,红节点的意义是与黑色父节点结合,表达着2-3-4树中的3,4节点。

转化规则

将2-3-4树转化为红黑树从以下三点可以实现:

  • 把2-3-4树中的每个2节点转化为红黑树的黑色节点。

![在这里插入图片描述](https://img-blog.csdnimg.cn/e49d2e58d9b74994a039c8230109d003.png?x-oss-process=image/watermark,type_ZHJvaWRzYW

  • 把每个3节点转化为一个子节点和一个父节点,子节点有两个自己的子节点:W和X或X和Y。父节点有另一个子节点:Y或W。哪个节点变成子节点或父节点都无所谓。子节点涂成红色,父节点涂成黑色。
  • 在这里插入图片描述

  • 把每个4节点转化为一个父节点和两个子节点。第一个子节点有它自己的子节点W和X;第二个子节点拥有子节点Y和Z。和 前面一样,子节点涂成红色,父节点涂成黑色。
  • 在这里插入图片描述

    2-3树到红黑树的转化(左倾红黑树)

    红黑树就是一种平衡树,但是理解红黑树之前,必须先了解另一种树,叫2-3树,红黑树背后的逻辑就是它。

    2-3树转红黑树,也可以说红黑树是2-3树和2-3-4树的另外一种表现形式。

    本文主要研究的还是2-3树,并且是2-3树中较为特殊的一种转化–左倾红黑树。顾名思义,左倾红黑树限制了如果在树中出现了红色节点,那么这个节点必须是左儿子。

    转化规则:

    • 2节点转化

    在这里插入图片描述

  • 3节点转化
  • 在这里插入图片描述
    3节点在左倾红黑树中只允许以左侧红色节点形式出现

    为了让大家更清晰的理解他们之间的关系,制作了一张红黑树转2-3树的示意图,如下:

    第一步:红黑树
    在这里插入图片描述
    第二步:将左倾的红色节点的链接旋转45°,使其与父节点保持同一水平线
    在这里插入图片描述
    最后:红色节点其实就是对2-3树中3节点的表示,所以将第2步中的链接转为3节点,得到原始的2-3树
    在这里插入图片描述
    到此,大家看到2-3树和红黑树的相互转换,和上面说的红黑树是2-3树或2-3-4树的另外一种表现形式。

    我们在了解红黑树的插入删除操作之前,需要先了解2-3树的插入删除操作,这样才能理解红黑树中染色和旋转背后的意义。

    让我们来看一下对于2-3树的插入。我们的插入操作需要遵循一个原则:先将这个元素尝试性地放在已经存在的节点中,如果要存放的节点是2节点,那么插入后会变成3节点,如果要存放的节点是3节点,那么插入后会变成4节点(临时)。然后,我们对可能生成的临时4节点进行分裂处理,使得临时4节点消失。

    2-3树的插入实现

    2-3树插入可以分为三种情况:

    • 对于空树,插入一个2节点即可
    • 插入节点到一个2节点的叶子上
    • 由于其本身只有一个元素,所以只需要将其转化为3节点就行。

    在这里插入图片描述

  • 往3节点中插入一个新数据
  • 因为3节点本就是就是2-3树的最大容量,因此需要拆分。分情况讨论如下所示:

    1、只有一个3节点的树,向其插入一个新key

    这个节点已经没有可插入的空间了。我们又不能把新key插在其空结点上(破坏了完美平衡)。为了将新key插入,我们先临时将新key存入一个4节点的临时节点上,使之成为一个4结点。创建一个4节点很方便,因为很容易将它转换为一颗由3个2节点组成的2-3树(如图所示),这棵树既是一颗含有3个节点的二叉查找树,同时也是一颗完美平衡的2-3树,其中所有空链接到根结点的距离都相等。

    在这里插入图片描述
    2、向一个父节点为2节点的3节点中插入数据

    假设未命中的查找结束于一个3节点,而它的父节点是一个2节点。在这种情况下我们需要在维持树的完美平衡的前提下为新key腾出空间。我们先像刚才一样构造一个临时的4节点并将其分解,但此时我们不会为中键创建一个新节点,而是将其移动至原来的父节点中。

    在这里插入图片描述
    这次转换并没有影响2-3树的主要性质,树仍然是有序的,因为6的key被移动到父节点中去了。

    3、向一个父节点为3节点中插入数据

    假设未命中的查找结束于一个3-结点,而它的父结点是一个3节点。
    我们再次和刚才一样构造一个临时的4节点并分解它,然后将它的中键插入它的父节点中。但父节点也是一个3节点,因此我们再用这个中键构造一个新的临时4节点,然后在这个节点上进行相同的变换,即分解这个父节点并将它的中键插入到它的父节点中去。

    在这里插入图片描述
    我们就这样一直向上不断分解临时的4节点并将中键插入更高的父节点,直至遇到一个2节点并将它替换为一个不需要继续分解的3节点。

    4、父节点到根节点均是3节点

    假设未命中的查找结束于一个3节点,而它的父结点是一个3节点。通过观察,到根节点都是满3节点。

    在这里插入图片描述

    最后我们发现(1、3)、(4、6)、(8、12)都是3节点,无法插入,意味着当前我们的树结构是三层已经不满足当前节点增加的需要了,于是将(1、3)拆分,(4、6)拆分,(8、12)拆分,树的深度增加一层。

    在这里插入图片描述
    事实上,这正对应了红黑树在插入的时候一定会把待插入节点涂成红色,因为红色节点的意义是与父节点进行关联,形成概念模型2-3树中的3节点或者临时4节点。

    而红黑树之所以需要在插入后进行调整,正是因为可能存在着2-3树概念模型中的创建临时4节点的情况。

    接下来让我们来看一下对于2-3树的删除。对于2-3树的删除我们主要要考虑待删除元素在2节点这种情况,因为如果待删除元素在3节点,那么可以直接将这个元素删除,而不会破坏2-3树的任何性质。

    2-3树的删除实现

    对于2-3树的删除操作,分为3种情况:

    1、所删除key在3节点中。

    如果待删除元素在3节点,那么可以直接将这个元素删除,不会影响到整棵树的其他节点结构。

    在这里插入图片描述
    2、删除的元素在2节点中,兄弟节点是3节点,此时从兄弟节点借一个元素到父节点,同时父节点下调一个元素到当前节点,使得当前节点成为一个3节点后,再进行删除。

    在这里插入图片描述
    3、删除的key在2节点,兄弟节点为2节点,父节点为3节点,此时父节点下移一个key,与当前节点以及当前节点的兄弟节点形成一个临时节点。

    在这里插入图片描述
    4、待删除的key在2节点,兄弟节点为2节点,父节点为2节点,此时父节点与当前节点以及当前节点兄弟节点形成一个临时4节点。

    在这里插入图片描述

    接下来我们讲解重点—红黑树

    红黑树

    在这里插入图片描述

    从上面的图中可以看出红黑树满足以下5个特征:

    • 每个节点只有两种颜色:红色和黑色。
    • 根节点是黑色的。
    • 每个叶子节点(NIL)都是黑色的空节点。
    • 从根节点到叶子节点,不会出现两个连续的红色节点。
    • 从任何一个节点出发,到叶子节点,这条路径上都有相同数目的黑色节点。

    红黑树作为一种自平衡的二叉查找树。

    那什么是平衡二叉查找树呢?
    百度百科中的定义:具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

    规则:二叉查找树中有一个元素X和两个指针域,X的左子树的所有键值都小于X的键值,X右子树的所有键值都大于X的键值。

    极端情况下,二叉查找树会退化成线性的链表,导致插入和查找的复杂度下降到 O(n) 。如下图所示:
    在这里插入图片描述
    这种情况的二叉树就是一个链表了,搜索时只能像链表一样线性遍历,丢失了二叉树应有的搜索性能。

    所以,这也是平衡二叉树设计的初衷。那么平衡二叉树如何保持”平衡“呢?根据定义,有两个重点,一是左右两子树的高度差的绝对值不能超过1,二是左右两子树也是一颗平衡二叉树。

    因此对二叉树进行平衡调整是很重要的一个环节,无论是AVL还是红黑树,它们本质上都是希望尽可能保证这颗二叉查找树中的元素尽量均衡的分布在树的两侧。

    二叉树实现步骤:当我们向一颗二叉查找树中插入一个X的key,X会一直与树中的节点进行大小比较,如果X小于当前key,就往左走,如果X大于当前key,就往右走,直到达到叶子节点,这个时候我们可以把X插入这颗二叉查找树了。

    在插入一个新的值时有可能导致树的不平衡,因此我们需要在插入后进行一次平衡调整,使得整棵树恢复到平衡的状态。

    而关于删除,上面讲解也说过了,下面在复习一遍。
    当我们需要从树中指定的位置取走一个key,而且在取走后还需要经过调整来使得整颗树满足平衡的性质,这个就牵扯到位置的改动,而且关联到很多的节点,这样效率太低了。

    后来有人提出一个观点:在不改动这个节点的位置将某个key节点删除后,它有两个最佳替代者,分别是有序序列中的前驱元素和后继元素,如下图:

    在这里插入图片描述

    对于要删除的key为4,它的前驱是1,后继是6,(6、7)是3节点,只需要用6来补位就行。

    也就是说,如果删除某个节点的时候,我们先找到它的前驱元素或者后继元素,将它的前驱元素直接填到待删除的节点,然后再把它的前驱元素或者后继元素删除。

    二叉树的旋转

    为了平衡一颗树,使其左右节点数目分布均匀保持平衡,会使用旋转的手段实现,而二叉树节点旋转一共有两种操作:左旋和右旋。

    • 左旋:是以节点的"右分支"为轴,进行逆时针旋转。

    在这里插入图片描述

  • 右旋:是以节点的“左分支"为轴,进行顺时针旋转。
  • 在这里插入图片描述

    理解了这些以后我们再去看红黑树的插入删除,就很容易理解旋转和染色背后的意义了。

    接下来我们以左倾红黑树作为此次红黑树插入和删除的演示。

    在这里插入图片描述

    如上图所示,左倾红黑树的插入主要分为三种情况:

    • 第一种:待插入元素比黑父节点大,插在了黑父节点的右边,而黑父节点左边是红色儿子节点。这种情况会导致在红黑树中出现右倾红节点。

    在这里插入图片描述
    为了符合红黑树特性,将分布在13左右的红色子节点染黑,13所在的节点染红

    在这里插入图片描述
    经过这种调整后,多出的红色上升到了13所在的节点,由于此时满足红黑树特性,不用在做调整了。

  • 第二种:待插入元素比红父j节点小,且红父节点自身就是左倾,看图就会明白,其实就是说红父节点和待插入元素同时靠在了左边,形成了连续的红色节点。
  • 在这里插入图片描述
    首先对13所在节点进行一次右旋,使得10占据13的位置。

    在这里插入图片描述
    这次右旋不会破坏黑色平衡,但是也没有解决连续红色的问题,接下来将10所在节点与13所在节点交换颜色。

    在这里插入图片描述
    这样目的是为了消除连续红色,并且这个操作依旧维持了黑色平衡。现在我们已经得到了第一种情况,直接按第一种情况处理即可。

  • 第三种:待插入元素比红父大,且红父自身就是左倾,看图就会明白。
  • 在这里插入图片描述
    对右倾的处理其实很简单,将10所在的节点进行左旋,使得出现连续的左倾红节点。

    在这里插入图片描述
    此时出现了第二种情况中,直接按照第二种情况的方式处理就行。

    在插入时,可以体会到左倾红黑树对于左倾的限制带来的好处,因为在原树符合红黑树定义的情况下,如果父亲是红的,那么它一定左倾。

    接下来是左倾红黑树的删除操作

    而左倾红黑树的删除需要借鉴上文中提到的前驱/后继节点,当我们要删除某个节点的时候选择它的前驱节点或者后继节点元素来替代它,转而删除它的前驱/后继节点。

    以下面的图为例:
    在这里插入图片描述

    假如我们需要对2进行删除。

    我们从当前的根节点出发,利于2-3树中预合并的策略逐层对红黑树进行调整。具体的做法是,每次都保证当前的节点是2-3树中的非2节点,如果当前节点已经是非2节点,那么直接跳过;如果当前节点是2节点,那么根据兄弟节点的状况来进行调整:

    • 如果兄弟是2节点,那么从父节点借一个元素给当前节点,然后与兄弟节点一起形成一个临时4节点。
    • 如果兄弟是非2节点,那么兄弟上升一个元素到父节点,同时父节点下降一个元素到当前节点,使得当前节点成为一个3节点。
    这样的策略能够保证最后走到待删除节点的时候,它一定是一个非2节点,我们可以直接将其元素删除。

    接下来如下图删除:

    在这里插入图片描述
    如上图:当前节点是2节点,需要合并,由于她是根节点,不存在父节点,所以直接把它和两个儿子节点合并成临时4节点并且下移一位。

    在这里插入图片描述
    如上图:本来当前节点应该指向15,由于15已经与33合并成临时4节点,因此15和33在同一高度,那么我们下降来到7所在的节点,而7节点是2节点,它的父节点是临时4节点,兄弟是2节点,因此父节点下调一个元素与当前节点和兄弟节点形成一个临时4节点。

    在这里插入图片描述
    如上图:当前节点现在是一个临时4节点了,我们继续下移到3所在的节点。

    在这里插入图片描述
    如上图:当前节点是一个2节点,它的父节点是临时4节点,兄弟是2节点,将父节点下移一个元素,与它的兄弟节点合并成一个临时4节点。

    在这里插入图片描述
    现在当前节点是一个临时4节点,如果想删除2,可以直接删除,在2-3树中删除一个非2节点的元素,直接删除就行。

    接下来要考虑的是修复工作,由于红黑树定义的限制,我们在插入或删除的过程中出现了一些本不该存在的红色右倾节点。以下为删除2数据以后的修复为例:

    在这里插入图片描述

    • 对7所在的节点进行一次左旋+重新染色即可修复右旋红节点,如下图:
    • 在这里插入图片描述
      在这里插入图片描述

    • 9没有右倾红节点,我们之间上升到15的节点,发现15存在右倾的红节点22,需要进行修复,同样是对15所在节点一次左旋+重新染色,如下图:
    • 在这里插入图片描述
      在这里插入图片描述

    • 22没有右倾红节点,我们直接上升到33的节点,发现33存在右倾的红节点80,需要进行修复,同样是对33所在节点一次左旋+重新染色。
    • 在这里插入图片描述

      在这里插入图片描述
      至此,本次对2所在节点的删除和修复工作已经全部结束了,当前的红黑树满足5大特性,不需要更多的调整。

      • 右倾转左倾是一个很基本的操作,事实上我们对于右倾的修复就是换了一种树形而已。一路回溯到当前根节点,直至路径中不再包含任何的红色右倾节点,至此修复工作全部完成。

      最后

      本篇文章主要介绍了红黑树的前世今生,让大家从本质上清楚的认识到红黑树中的各种操作来源,虽然只是介绍了红黑树相对简单的左倾红黑树,但是如果能够将左倾红黑树认识的很清楚,那么对于理解右倾红黑树也就容易多了。

      本文我也是一边在学习理解红黑树一边写这边文章,希望和大家一起进去,不足之处还望见谅,多多指教。

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值