一、介绍
红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构。
1972年出现,当时被称之为平衡二叉树B树。后来,1978年被修改为如今的 “ 红黑树 ”。
红黑树是一种特殊的二叉查找树,红黑树的每一个节点上都有存储位表示节点的颜色,
每一个节点可以是红色或黑色,不能是其他颜色;红黑树不是高度平衡的,它的平衡是通过 “红黑规则” 进行实现的。
如下图就是一颗红黑树。
![image-20240427103543025](https://img-blog.csdnimg.cn/img_convert/9c7ebdb331edb1f9934ab4a65f2188f0.png)
红黑树是一颗二叉查找树,但是它不是一颗平衡二叉树!
平衡二叉树它是高度平衡的,当左右子树高度差超过1时,就会通过旋转保持平衡。因此平衡二叉树的查找效率会比较高,但是它本身也是有一点小弊端的:在添加节点的时候,由于旋转的次数太多了,就会造成添加节点的时间浪费。
但是我们现在要学习的红黑树就是优化了平衡二叉树,改变了规则。
当添加一个节点的时候,如果不符合我自己定义的红黑规则了,才进行旋转或者其他操作,红黑规则会比平衡二叉树的 当左右子树高度差超过1时,就会通过旋转保持平衡
这个规则要轻松一点,没有平衡二叉树这么严格。
![image-20240427103953147](https://img-blog.csdnimg.cn/img_convert/ff721896a1ecb0fa5e9d95e465be4e93.png)
二、红黑规则
1、每一个节点有可能是红色,也可能是黑色的,不能是其他。
2、根节点必须是黑色。
3、如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为 Nil
,这些 Nil
视为叶节点(也叫叶子结点),每个叶节点(Nil)是黑色的。
4、如果某一个节点是红色,那么它的子节点必须是黑色(即不能出现两个红色节点相连的情况)。
5、对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
1、每一个节点有可能是红色,也可能是黑色的,不能是其他。
在之前我们学习的二叉树中,每一个节点中一共有四部分元素
在红色树中,它又多了一个颜色属性,用来记录红黑树结点的颜色,要么是黑色,要么是红色。
![image-20240427105248317](https://img-blog.csdnimg.cn/img_convert/05d32d807374fe95ac9ab9547a651bc5.png)
2、如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为 Nil
,这些 Nil
视为叶节点(也叫叶子结点),每个叶节点(Nil)是黑色的
如下图就是一颗红色树。
![image-20240427103543025](https://img-blog.csdnimg.cn/img_convert/9c7ebdb331edb1f9934ab4a65f2188f0.png)
我们以 6节点
为例,来看看 6
节点里面是什么样子的。
在 6节点
中它首先要记录父节点的地址值,在中间的值中,记录的就是 数字6
,颜色就是当前节点真实的颜色。
主要的来看左子节点和右子节点。
![image-20240427105453542](https://img-blog.csdnimg.cn/img_convert/fbd43bc7cd9e71c5be4667a2ad4ee273.png)
在刚刚我们看见了,它是没有左子也没有右子的,因此它记录的位置就需要记录为空,专业叫做 Nil
,你可以把它理解成就是一个 Null
。
我们要注意的是,这些 Nil
虽然表示的是空,但是在下面也会挂一个空节点,视为叶子结点,是黑色的。
只不过这些叶子结点里面是没有任何数据的。
![image-20240427105841768](https://img-blog.csdnimg.cn/img_convert/3c21b7b5016deb22219b377fa110c254.png)
这些叶子结点也是灭有什么意义的,我们平时在 查找数据 / 遍历红色树
的时候,也不会考虑叶子结点。
叶子结点唯一的作用就是待会将上面 第五条红色规则
的时候,用于判断当前红色树是否满足要求的。
4、如果某一个节点是红色,那么它的子节点必须是黑色(即不能出现两个红色节点相连的情况)
这句话比较拗口,我们只需要看后面一句话就行了:不能出现两个红色节点相连的情况。
因此,如果两个节点都是黑色,那么是可以在一块的。一红一黑也是可以在一块。只有两个红的不能连在一起。
5、对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点
后代节点:以根节点 13
为例,下面所有的节点,都是 13节点
的后代。
![image-20240427110343558](https://img-blog.csdnimg.cn/img_convert/80bd2a1d6ab438f68db02f4578c86b25.png)
例如以 17节点
为例,17
下面的这些节点都是 17节点
的后代节点。
![image-20240427110439557](https://img-blog.csdnimg.cn/img_convert/aa57a5cab719524e1a015865247c9936.png)
后代叶节点:以 13
为例,只有下面的 Nil
才是 13节点
的后代叶节点
![image-20240427110742270](https://img-blog.csdnimg.cn/img_convert/070b6115eee6ad5c422cf2fd4b5045dd.png)
例如以 17节点
为例,17
下面框起来的的这些节点都是 17节点
的后代叶节点。
![image-20240427110829444](https://img-blog.csdnimg.cn/img_convert/a7ce3ce1bd2e941793fc72b99bcebf0d.png)
简单路径:以13为例,从13走到8,再走到1,最后走到 Nil
,这就是一条简单路径。
核心:只能往前,不能回头。
![image-20240427111046425](https://img-blog.csdnimg.cn/img_convert/dc37f3765c7a85c703a164e81b9d56fa.png)
找到简单路径后,这些路径路径上黑色节点的数目是一样的。如上图路径,有三个黑色节点。
因此我们刚刚说的叶子节点 Nil
,它本身实际上是没有什么本事的含义的,就是在第五条的规则中用来统计个数的。
三、添加节点默认是红色的,因为它的效率高
我们在添加节点的时候需要知道它最基础的要求。
默认颜色:添加节点默认是红色的,因为它的效率高。
假设现在我们要添加 20、18、23
这三个节点。
1)假设默认添加的节点默认是黑色的
首先添加第一个节点:20
,此时是没有任何问题的,满足红黑规则。
![image-20240427111725481](https://img-blog.csdnimg.cn/img_convert/09fc437f01ba439ae32109310342838d.png)
此时来添加第二个节点 18
,18
和 20
比较,可以发现 18
比较小,因此需要存入 20
的左边。
注意,此时就已经违背了红色规则:对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
。
以 20根节点
为例,它到左边的 Nil
,在这条线上,它一共是三个黑色的节点,但是到右边的 Nil
上,它只有两个黑色的节点。
因此此时不满足这条红黑规则了。
不满足就需要调整:将添加的 18节点
变成红色的就行了。
![image-20240427112432581](https://img-blog.csdnimg.cn/img_convert/6af943e0962dc005cffd15cb6348b95c.png)
继续往下,来添加第三个节点 23
,23
跟 20
比较,发现比 20
大,因此要存它的右边。
存完了后它其实也违背了刚刚的第五个红黑规则:20
到左边的 Nil
,在这条简单路径上它有两个黑色节点;20
到右边的 Nil
,在这条简单路径上一共有三个黑色节点,因此违背了红黑规则。
此时就需要调整:将 23
变成红色的就行了。
由此可见,如果添加的节点默认是黑色的,添加三个元素,就需要调整两次。
2)假设默认添加的节点默认是红色的
第一个节点 20
作为根节点,此时就违背了第二条规则:根节点必须是黑色的
,因此此时直接将根节点 20
变成黑色的就行了。
![image-20240427113633698](https://img-blog.csdnimg.cn/img_convert/02d5ae29fa405b3876c4b2281cbd6a99.png)
再来添加第二个:18节点
,由于 18
小,因此需要存在 20节点
的左边。
![iz6kr-y7tlc](https://img-blog.csdnimg.cn/img_convert/b470bbefda21e17a52c57c2b315b5294.gif)
添加完成后,并没有破坏红黑规则,因此此时不需要进行任何调整。
同理,添加第三个节点 23
,此时 23
比 20
大,因此存右边。
![1hnpj-8t1eo](https://img-blog.csdnimg.cn/img_convert/baa54cdad777857a308b69567228b14a.gif)
存完后,此时也没有违背红色规则。
因此,添加三个节点,默认是红色的话,只需要调整一次。
此时我们就可以得出一个结论:添加节点时,如果默认颜色是红色的话,这样它的效率要更高。
四、红黑树添加节点的规则
我们在往红黑树添加节点的时候,除了要知道,默认是红色之外,其实还有很多种情况会违背红黑规则,不同的情况处理方案是不一样的。
接下来我们系统学习,当我们添加多个节点的时候,红黑树如何调整来保证完全符合红黑规则。
例如添加下面的节点,添加节点默认是红色的,因为它的效率高。
![image-20240427114805185](https://img-blog.csdnimg.cn/img_convert/0c2683a9ae6b5ef5a1e5f794cae5c993.png)
1)当前节点为根节点
首先添加 20
作为根节点。
![image-20240427115110548](https://img-blog.csdnimg.cn/img_convert/e5cbd636c24ad6fa5c46b7e89e8a9170.png)
此时它就违背了第二条规则 根节点必须是黑色
,此时就可以直接去刚刚的图找处理方案:
如果你在添加节点的时候是根节点,就用蓝色的处理方案 直接变成黑色
。
![image-20240427115216936](https://img-blog.csdnimg.cn/img_convert/00ed6c5c2f7c12fc969779a8ef0d0773.png)
因此此时将 20
变成黑色的,就处理完毕了。
![image-20240427115251991](https://img-blog.csdnimg.cn/img_convert/911ee9c215686b8a4af844b128479959.png)
2)当前节点不为根节点,父节点为黑色
接下来添加 18节点
,18
比 20
小,因此存左边。
![image-20240427115351567](https://img-blog.csdnimg.cn/img_convert/2a86ed8be730bbd8929c699b8df79fb7.png)
此时是没有违背任何的红黑规则的。
此时在图中也有一个处理方案:如果你添加的节点非根,非根就看它的父节点,如果父节点是黑色的,那么不需要任何操作。
在这我们就知道了,18
的父节点是 20
,20
是黑色的,此时我们不需要进行任何的操作。
![image-20240427115622071](https://img-blog.csdnimg.cn/img_convert/207c887d0a47047ed1afd4be0266aaa0.png)
继续往下,添加 23
,23
比较后,需要存 20
的右边,由于 23
的父节点 20
也是黑的,因此也不需要进行任何操作。
这个就是前三个节点,相对来讲比较简单。
![image-20240427115832645](https://img-blog.csdnimg.cn/img_convert/4fa38a1f8741308824c712dbd6294573.png)
继续往下,要来添加第四个节点,从这开始,难度就要上升了。
3)当前节点不为根节点,父节点为红色,叔叔节点也为红色
开始存 22
,首先跟根节点 20
比,发现比 20
大,因此存 20
右边。
右边跟 23
又比了一下,22
小于 23
,因此要存 23
的右边。
![image-20240427120157546](https://img-blog.csdnimg.cn/img_convert/fa3f5369d3898959bf04f383c48d1b6c.png)
此时就违背了第四条:两个红色节点不能相连
。
开始看解决方案:如果你添加的节点是非根,就要看父节点,由于父节点是红色的,红色的就需要看叔叔节点。
例如我们刚刚添加的 22
的父节点是 23
,那么叔叔节点就是 18
,它的颜色是红色。
由于叔叔节点是红色,根据下图,可以看见一共有四种步。
![image-20240427120555091](https://img-blog.csdnimg.cn/img_convert/149ba655549fe3447e2d7540ce98a4c1.png)
① 将 “父” 设置为黑色,将 “叔叔” 设置为黑色
② 将 “祖父” 设为 “红色”
22
的祖父就是 20
,因此需要将 20
变成红色。
![image-20240427121355143](https://img-blog.csdnimg.cn/img_convert/346475f22731f5e9a3e3a319c5c25cb6.png)
然后下面有两个相对立的。
③ 如果祖父为根,再将根变回黑色
由于此时祖父 20
是根,因此此时需要变回来。
4)当前节点不为根节点,父节点为黑色
往下,添加 17节点
,比了一下它发现,17
要比 20
小,所以应该存 20
的左边。
继续跟 18
节点比较,发现比 18
小,因此存 18
的左边,当做是 18
的左子节点。
再来对比一下规则,看看我们是否要来进行处理
如果我们添加的节点是非根,并且父节点是黑色,则不需要进行任何的操作。
因此 17
现在就放在这,不需要进行任何操作。
接下来是同样的道理,当我们添加 24
和 19
时,它们的父节点也都是黑色的,因此也不需要进行任何的调整。
![image-20240427131917031](https://img-blog.csdnimg.cn/img_convert/aab8f562e4efed2511da3e2958431515.png)
当前节点不为根节点,父节点为红色,叔叔节点也为红色
接下来再来添加两个节点,添加这两个节点的时候就有点难了。
![image-20240427132022133](https://img-blog.csdnimg.cn/img_convert/5bcc1aa87d9ab56e8d0da7c5bcefe7da.png)
首先添加 15节点
,结果对比,它应该是添加在 17节点
的左边。
![image-20240427133459594](https://img-blog.csdnimg.cn/img_convert/baa215ea28f18cf9e3ed833f26c4f2b5.png)
这个时候它的父节点是红色的,叔叔节点也是红色的,此时就需要使用到下面深红色的处理方案。
![image-20240427120555091](https://img-blog.csdnimg.cn/img_convert/149ba655549fe3447e2d7540ce98a4c1.png)
它需要将 父、叔、祖父...
都操作一下。
① 将 “父” 设置为黑色,将 “叔叔” 设置为黑色
② 将 “祖父” 设为 “红色”
③ 如果祖父为非根,将祖父设置为当前节点再进行其他判断
此时祖父 18
不是根节点,所以此时我们要站在祖父节点的基础上,再次进行判断。
现在 18节点
的父节点是黑色的,此时我们需要再去表中找对应的处理方案。
可以看见,父节点是黑色的,不需要做任何操作,因此此时调整已经结束。
5)当前节点不为根节点,父节点为红色,叔叔节点为黑色,并且当且节点是父节点的左孩子
继续往下,添加 14节点
,结果对比,14节点
应该放在 15
的左边,当做是 15节点
的左子节点。
![image-20240427134535645](https://img-blog.csdnimg.cn/img_convert/c104bc3af571288e3be3bccac1e51a6d.png)
这个时候它其实也违背了红黑规则:不能出现两个红色相连的情况
。
因此此时我们需要调整:14
的父节点是 红
,叔叔节点是 Nil
,即黑色,而且 14
是 15
的左子节点,因此要采取的是下面深灰色的处理方案。
前两步如下图
第三步:以祖父为支点进行右旋
在旋转的时候是不需要考虑 Nil
叶子结点的,直接转就行了,转完后再将叶子结点补上接口。
6)当前节点不为根节点,父节点为红色,叔叔节点为黑色,并且当且节点是父节点的右孩子
还有一种情况我们需要知道。
先将 14节点
去掉,假设我们刚刚添加的不是 14节点
,而是 16节点
。
由于 16节点
应该添加到 15节点
的右边,当做是 15
的右子节点。
![image-20240427135858540](https://img-blog.csdnimg.cn/img_convert/d06b0cabd780084d9ecdaff7ac77d84d.png)
根据表中解决方案:16
的父 15
是红色的,它的叔叔是 15
旁边的 Nil
,是黑色的,并且当前节点是父的右孩子,因此需要使用下面绿色的处理方案
把父作为当前节点,并左旋,再进行判断
在判断的时候,我们需要站在 15
的基础上再来看问题了,因为在刚刚在调整的时候,它是将父节点作为当前节点。
15
的父节点是红色的,叔叔节点是黑色,而且 15
是 16
的左子节点,因此就需要采取下面深灰色的处理方案。
将父节点变成黑色,将组父节点变成红色。
最后以祖父17为支点进行右旋。
旋转完毕后,整棵树就变成了下面这种情况
要注意的是,有两个地方需要 再进行其他判断
,这一点一定要注意。
通过这两个情况知道了,有时候往红黑树中添加节点,不是调整一次就行了,有可能要调整两次,也有可能要调整三次,或者更多。
五、总结
红黑树增删改查的性能都很好。
但是有同学就觉得:处理方案这么多这么复杂,性能为什么还更好?
因为在这些处理方案中,真正浪费时间的其实是旋转,在红黑树中,更多的处理方案仅仅是修改颜色而已,修改颜色其实是不浪费时间的,它改变的仅仅是变量中记录的值而已。
并且在这些处理方案中,并不是每次都需要进行旋转的,旋转的次数跟平衡二叉树比,要少太多了,因此红黑树增删改查的性能都很好。