红黑树笔记

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也是红节点,那么就要先对节点9进行左旋,然后对左旋后的节点15的父节点(假设为节点X)进行右旋,并修改节点15为黑色,修改节点X为红色。然后我们就会发现又回到了场景一,之后继续按照情况1的规则继续处理即可。

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

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

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

首先对红父的父亲进行一次右旋,这次右旋不会破坏黑色平衡,但是也没有解决连续红色的问题。

接下来将12所在节点与15所在节点交换颜色,这样的目的是为了消除连续红色,并且这个操作依旧维持了黑色平衡。现在我们已经得到了情况1的场景,直接按情况1处理即可。

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

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

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

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

删除操作

删除节点要保证2点:不能破坏树的有序性、不能破坏树的平衡性。流程上可以分为2步,第一步向下递归,对红黑树进行预调整,删除目标节点。第二步向上回溯,修复预调整阶段被破坏的红黑树。

如果要删除的节点是红黑树中的中间节点,那么删除节点后树的调整会非常复杂,因为要同时考虑父节点与子节点的情况。有一种简单的方法是,需要删除某个节点时,不直接删除该节点,而是改为删除它的前驱或后继节点,把删除节点的值直接赋值给当前要删除的节点。假设改为删除它的后继节点,那么情况就可以简化为删除叶子节点或者只包含一个子节点的节点,这可以大大简化后续需要执行的操作。

向下递归

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

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

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

向上回溯

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

记录哪些笔记?能够让人很久以后依然能够比较容易看懂?

  1. 红黑树实际上是2-3树的一种实现
  2. 介绍红黑树的5条定义
  3. 能不能不介绍左旋右旋?
  4. 介绍下什么是『临时4节点』
  5. 直接写插入和删除的场景?

2-3-4树 -> 标准红黑树

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

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

参考链接

红黑树-维基百科

红黑树_百度百科

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

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

相关推荐
秃头佛爷23 分钟前
Python学习大纲总结及注意事项
开发语言·python·学习
阿伟*rui24 分钟前
配置管理,雪崩问题分析,sentinel的使用
java·spring boot·sentinel
待磨的钝刨24 分钟前
【格式化查看JSON文件】coco的json文件内容都在一行如何按照json格式查看
开发语言·javascript·json
浮生如梦_1 小时前
Halcon基于laws纹理特征的SVM分类
图像处理·人工智能·算法·支持向量机·计算机视觉·分类·视觉检测
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck2 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei2 小时前
java的类加载机制的学习
java·学习
励志成为嵌入式工程师3 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
捕鲸叉4 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer4 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法