红黑树笔记

文章介绍了2-3树和左倾红黑树的关系,重点讲解了2-3树如何转化为红黑树,以及插入和删除操作中的三种情况。通过对比,解释了为什么直接实现2-3树复杂且效率低,转而选择红黑树作为更简洁的实现方式。
摘要由CSDN通过智能技术生成

2-3树 -> 左倾红黑树

红黑树实际上是2-3树的一种基于BST的实现。普通二叉搜索树(BST)中的每一个节点,只有一个键,两条链接(两个子节点),这种节点被称为2节点。2-3树中,引入了一个3节点的概念,它含有两个键,三条链接。左链接指向的键都小于该节点,中链接指向的键介于该节点的两个键之间,右链接指向的键都大于该节点。同理我们可以构造出4节点、5节点,在这样的树中,空节点到根节点的距离都是相同的,这样的树就是一棵平衡的2-3搜索树。

由于2-3树的新节点插入不是直接插入子节点,而是直接插入临近节点位置,然后判断是否需要拆分节点,拆分后向上合并节点,因此2-3树的生长方向是自底向上的,这也就保证了2-3树始终是平衡的。即使连续插入有序列表,生成出来的2-3树依然是高度平衡的。

节点的分解都在树的局部进行,因此分解操作不会影响树的平衡性和有序性。

2-3树实现的难点在于,它需要额外的数据类型来维护3节点,以及处理失衡的结构时,对不同类型的节点进行频繁的转换。

一种简化2-3树的思路

一种简化2-3树的思路是用2节点替换3节点,同时又能保持树的平衡性。

一棵标准2-3树

拆分3节点并标记左侧节点为红色

就得到了一棵红黑树(左倾红黑树)

左倾红黑树

为了保证红黑树跟2-3树完全等价,我们需要以下定义:

  • 红链接都是左链接
  • 任何节点不会同时和2个红色节点相连

满足这2个条件的红黑树被称为左倾红黑树,它的实现相对标准红黑树更为简单。

红黑树的五条性质

  • 节点是黑色或红色
  • 根节点只能是黑色
  • 所有叶子节点都是黑色(NIL)
  • 不能出现连续的红色节点
  • 任意节点到其每个叶子节点的每条路径都包含相同数量的黑色节点

若根节点为2节点,那其本身就是黑色节点,若根节点为3节点,那么黑色节点就是其中的较大元素,因此根节点总是黑色。

根据2-3树转化到红黑树的过程就可以很直观地看到,不会出现连续的红色节点,即使是基于2-3-4树实现的红黑树,4节点在红黑树中也是表现为一个黑色节点带两个红色子节点,因此一定不会出现连续的红色节点。

根据2-3树转化到红黑树的过程,可以看出红色节点总是依附黑色父节点而存在,因此在红黑树中只有黑色节点才真正贡献高度。2-3树本身具备高度平衡的特点,反映到红黑树中就是黑色节点完美平衡

什么是4节点?

从结构上来看,简单理解,一个黑色节点带两个红色子节点就可以认为是一个4节点。下图是4节点分解的完整过程。

插入操作的3种情况

下面介绍下左倾红黑树插入新节点的三种可能的情况。在介绍之前,先解释三个问题。

什么是左旋?

左旋就是围绕某个节点进行向左的旋转操作,本质就是把节点的右子节点变成新的根节点。

什么是右旋?

右旋就是围绕某个节点进行向右的旋转操作,本质就是把节点的左子节点变成新的根节点。

新插入的节点是什么颜色?  

先说结论:红色。这主要是为了维护红黑树的性质,前面介绍了红黑树的五大性质,其中一条就是每个节点到所有叶子节点的路径上黑色节点个数相同。如果新插入的节点是黑色,就可能直接违反这条性质,破坏红黑树的平衡性。插入红色节点,可以避免破坏平衡性,在此基础上,可以再通过旋转和染色来维护红黑树的其他性质。

① 情况1(出现左右两个红色子节点)

插入前黑父左边是红色节点,待插入节点比黑父大,插在了黑父的右边,此时出现右倾红节点。

注意,这种情况对应着2-3树中出现了临时4节点,我们在2-3树中的处理,是将这个临时4节点分裂,左右元素各自形成一个2节点,中间元素上升到上层跟父节点结合。所以,我们在红黑树中的动作是,将原本红色的左右儿子染黑(左右分裂),将黑父染红(等待上升结合)。

补充下特殊场景的处理,假设本次处理完成后,发现节点9也是红节点,那又变成了情况3,直接按照情况3的处理方式进行处理即可,情况3处理完成后,会发现又变成了情况2,我们再按照情况2进行处理,处理完之后,会发现又回到了情况1,之后再按照情况1的规则继续处理即可。

如此循环往复,最终执行完成后,我们就会发现红黑树恢复了符合五大原则的平衡状态。

② 情况2(左侧出现连续红色子节点)

待插入节点比红父小,且红父本身就是左倾红节点,也就是说,两个红节点靠在左边形成了连续的红节点。

这种情况我们需要用两步来调整。由于我们插入的是红色节点,其实不会破坏黑色完美平衡,所以要注意的是在旋转和染色的过程种继续保持这种完美黑色平衡

首先对红父的父亲进行一次右旋,这次右旋不会破坏黑色平衡,但是也没有解决连续红色的问题。
接下来将12所在节点与15所在节点交换颜色,这样的目的是为了消除连续红色,并且这个操作依旧维持了黑色平衡。现在我们已经得到了情况1的场景,直接按情况1处理即可。

③ 情况3(出现红色右节点)

待插入元素比红父大,且红父自身就是左倾。也就是说插入的这个节点形成了一个右倾的红色节点,对右倾的处理很简单,将红父进行一次左旋,就能使得右倾红节点变为左倾,现在出现了连续的左倾红节点,直接按情况2处理即可。

在插入时,可以体会到左倾红黑树对于左倾的限制带来的好处,因为在原树符合红黑树定义的情况下,如果父亲是红的,那么它一定左倾,同时也不用考虑可能存在的右倾兄弟(如果有,那说明原树不满足红黑树定义)。

这种限制消除了很多需要考虑的场景,让插入变得更加简单。

删除操作

删除节点要保证2点:不能破坏树的有序性、不能破坏树的平衡性。

流程上可以分为2步,第一步向下递归,对红黑树进行预调整,删除目标节点。第二步向上回溯,修复预调整阶段被破坏的红黑树。

如果要删除的节点是红黑树中的中间节点,那么删除节点后树的调整会非常复杂,因为需要同时考虑父节点与子节点的情况,这里我们不考虑直接删除的方法。

有一种简单的方法是,需要删除某个节点时,不直接删除该节点,而是改为删除它的前驱或后继节点,把删除节点的值直接赋值给当前要删除的节点。假设删除的是后继节点,那么情况就可以简化为删除叶子节点或者只包含一个子节点的节点,这可以大大简化后续需要执行的操作。

以上图为例,如果需要删除的节点是 7,那么先找到右子树的最小节点 8,把节点 7 的值修改为 8,然后开始向下递归删除节点 8,删除完成后,再向上回溯,恢复删除过程中被破坏的树结构,删除操作就完成了。

向下递归

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

  • 如果兄弟是2节点,那么从父节点借一个元素给当前节点,然后与兄弟节点一起形成一个临时4节点。
  • 如果兄弟是非2节点,那么兄弟上升一个元素到父节点,同时父节点下降一个元素到当前节点,使得当前节点成为一个3节点。

这样的策略能够保证最后走到待删除节点的时候,它一定是一个非2节点,我们可以直接将其元素删除。

假设我们需要删除的节点的右子树如图所示,那么对该节点的删除实际上转为了对 2 的删除。

向上回溯

接下来要考虑的是修复工作,由于红黑树定义的限制,我们在调整的过程中出现了一些本不该存在的红色右倾节点(因为生成了概念模型中的临时4节点),于是我们顺着搜索的方向向上回溯,如果遇到当前节点具备右倾的红色儿子,那么对当前节点进行一次左旋,这时原本的右儿子会来到当前节点的位置,然后将右儿子与当前节点交换颜色,我们就将右倾红节点修复成了左倾红节点,同时我们并没有破坏黑色节点的平衡。

为什么不直接实现2-3树或者2-3-4树?

因为直接实现太过复杂,需要处理不同节点间的转换,分解4节点和5节点需要处理的情况太多,需要考虑4节点的位置情况,需要考虑父节点是2节点还是3节点,4节点是左节点、右节点还是中间节点等等,而且实现后需要大量额外的开销,实际性能并不理想,因此实际应用很少。

参考链接

红黑树-维基百科

红黑树_百度百科

《算法》第4版 - chapter 3.3 红黑树_哔哩哔哩_bilibili

图解:什么是红黑树? - 知乎

  • 52
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值