【Java】红黑树

一、介绍

红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构。

1972年出现,当时被称之为平衡二叉树B树。后来,1978年被修改为如今的 “ 红黑树 ”。

红黑树是一种特殊的二叉查找树,红黑树的每一个节点上都有存储位表示节点的颜色,

每一个节点可以是红色或黑色,不能是其他颜色;红黑树不是高度平衡的,它的平衡是通过 “红黑规则” 进行实现的。

如下图就是一颗红黑树。

image-20240427103543025

红黑树是一颗二叉查找树,但是它不是一颗平衡二叉树!

平衡二叉树它是高度平衡的,当左右子树高度差超过1时,就会通过旋转保持平衡。因此平衡二叉树的查找效率会比较高,但是它本身也是有一点小弊端的:在添加节点的时候,由于旋转的次数太多了,就会造成添加节点的时间浪费。

但是我们现在要学习的红黑树就是优化了平衡二叉树,改变了规则。

当添加一个节点的时候,如果不符合我自己定义的红黑规则了,才进行旋转或者其他操作,红黑规则会比平衡二叉树的 当左右子树高度差超过1时,就会通过旋转保持平衡 这个规则要轻松一点,没有平衡二叉树这么严格。

image-20240427103953147

二、红黑规则

1、每一个节点有可能是红色,也可能是黑色的,不能是其他。

2、根节点必须是黑色。

3、如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为 Nil,这些 Nil 视为叶节点(也叫叶子结点),每个叶节点(Nil)是黑色的。

4、如果某一个节点是红色,那么它的子节点必须是黑色(即不能出现两个红色节点相连的情况)。

5、对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。


1、每一个节点有可能是红色,也可能是黑色的,不能是其他。

在之前我们学习的二叉树中,每一个节点中一共有四部分元素

在红色树中,它又多了一个颜色属性,用来记录红黑树结点的颜色,要么是黑色,要么是红色。

image-20240427105248317

2、如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为 Nil,这些 Nil 视为叶节点(也叫叶子结点),每个叶节点(Nil)是黑色的

如下图就是一颗红色树。

image-20240427103543025

我们以 6节点 为例,来看看 6 节点里面是什么样子的。

6节点 中它首先要记录父节点的地址值,在中间的值中,记录的就是 数字6,颜色就是当前节点真实的颜色。

主要的来看左子节点和右子节点。

image-20240427105453542

在刚刚我们看见了,它是没有左子也没有右子的,因此它记录的位置就需要记录为空,专业叫做 Nil,你可以把它理解成就是一个 Null

我们要注意的是,这些 Nil 虽然表示的是空,但是在下面也会挂一个空节点,视为叶子结点,是黑色的。

只不过这些叶子结点里面是没有任何数据的。

image-20240427105841768

这些叶子结点也是灭有什么意义的,我们平时在 查找数据 / 遍历红色树 的时候,也不会考虑叶子结点。

叶子结点唯一的作用就是待会将上面 第五条红色规则 的时候,用于判断当前红色树是否满足要求的。


4、如果某一个节点是红色,那么它的子节点必须是黑色(即不能出现两个红色节点相连的情况)

这句话比较拗口,我们只需要看后面一句话就行了:不能出现两个红色节点相连的情况。

因此,如果两个节点都是黑色,那么是可以在一块的。一红一黑也是可以在一块。只有两个红的不能连在一起。


5、对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点

后代节点:以根节点 13 为例,下面所有的节点,都是 13节点 的后代。

image-20240427110343558

例如以 17节点 为例,17 下面的这些节点都是 17节点 的后代节点。

image-20240427110439557

后代叶节点:以 13 为例,只有下面的 Nil 才是 13节点 的后代叶节点

image-20240427110742270

例如以 17节点 为例,17 下面框起来的的这些节点都是 17节点 的后代叶节点。

image-20240427110829444

简单路径:以13为例,从13走到8,再走到1,最后走到 Nil,这就是一条简单路径。

核心:只能往前,不能回头。

image-20240427111046425

找到简单路径后,这些路径路径上黑色节点的数目是一样的。如上图路径,有三个黑色节点。

因此我们刚刚说的叶子节点 Nil,它本身实际上是没有什么本事的含义的,就是在第五条的规则中用来统计个数的。


三、添加节点默认是红色的,因为它的效率高

我们在添加节点的时候需要知道它最基础的要求。

默认颜色:添加节点默认是红色的,因为它的效率高。

假设现在我们要添加 20、18、23 这三个节点。


1)假设默认添加的节点默认是黑色的

首先添加第一个节点:20,此时是没有任何问题的,满足红黑规则。

image-20240427111725481

此时来添加第二个节点 181820 比较,可以发现 18 比较小,因此需要存入 20 的左边。

xehlm-gp0mu

注意,此时就已经违背了红色规则:对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。

20根节点 为例,它到左边的 Nil,在这条线上,它一共是三个黑色的节点,但是到右边的 Nil 上,它只有两个黑色的节点。

因此此时不满足这条红黑规则了。

image-20240427112329982

不满足就需要调整:将添加的 18节点 变成红色的就行了。

image-20240427112432581

继续往下,来添加第三个节点 232320 比较,发现比 20 大,因此要存它的右边。

tffnn-n2rdr

存完了后它其实也违背了刚刚的第五个红黑规则:20 到左边的 Nil ,在这条简单路径上它有两个黑色节点;20 到右边的 Nil,在这条简单路径上一共有三个黑色节点,因此违背了红黑规则。

image-20240427113142105

此时就需要调整:将 23 变成红色的就行了。

image-20240427113223645

由此可见,如果添加的节点默认是黑色的,添加三个元素,就需要调整两次。


2)假设默认添加的节点默认是红色的

第一个节点 20 作为根节点,此时就违背了第二条规则:根节点必须是黑色的,因此此时直接将根节点 20 变成黑色的就行了。

image-20240427113633698

再来添加第二个:18节点,由于 18 小,因此需要存在 20节点 的左边。

iz6kr-y7tlc

添加完成后,并没有破坏红黑规则,因此此时不需要进行任何调整。

同理,添加第三个节点 23,此时 2320 大,因此存右边。

1hnpj-8t1eo

存完后,此时也没有违背红色规则。

因此,添加三个节点,默认是红色的话,只需要调整一次。

此时我们就可以得出一个结论:添加节点时,如果默认颜色是红色的话,这样它的效率要更高。


四、红黑树添加节点的规则

我们在往红黑树添加节点的时候,除了要知道,默认是红色之外,其实还有很多种情况会违背红黑规则,不同的情况处理方案是不一样的。

image-20240427114528485

接下来我们系统学习,当我们添加多个节点的时候,红黑树如何调整来保证完全符合红黑规则。

例如添加下面的节点,添加节点默认是红色的,因为它的效率高。

image-20240427114805185

1)当前节点为根节点

首先添加 20 作为根节点。

image-20240427115110548

此时它就违背了第二条规则 根节点必须是黑色,此时就可以直接去刚刚的图找处理方案:

如果你在添加节点的时候是根节点,就用蓝色的处理方案 直接变成黑色

image-20240427115216936

因此此时将 20 变成黑色的,就处理完毕了。

image-20240427115251991

2)当前节点不为根节点,父节点为黑色

接下来添加 18节点1820 小,因此存左边。

image-20240427115351567

此时是没有违背任何的红黑规则的。

此时在图中也有一个处理方案:如果你添加的节点非根,非根就看它的父节点,如果父节点是黑色的,那么不需要任何操作。

在这我们就知道了,18 的父节点是 2020 是黑色的,此时我们不需要进行任何的操作。

image-20240427115622071

继续往下,添加 2323 比较后,需要存 20 的右边,由于 23 的父节点 20 也是黑的,因此也不需要进行任何操作。

这个就是前三个节点,相对来讲比较简单。

image-20240427115832645

继续往下,要来添加第四个节点,从这开始,难度就要上升了。


3)当前节点不为根节点,父节点为红色,叔叔节点也为红色

开始存 22,首先跟根节点 20 比,发现比 20 大,因此存 20 右边。

右边跟 23 又比了一下,22 小于 23,因此要存 23 的右边。

image-20240427120157546

此时就违背了第四条:两个红色节点不能相连

开始看解决方案:如果你添加的节点是非根,就要看父节点,由于父节点是红色的,红色的就需要看叔叔节点。

例如我们刚刚添加的 22 的父节点是 23,那么叔叔节点就是 18,它的颜色是红色。

由于叔叔节点是红色,根据下图,可以看见一共有四种步。

image-20240427120555091

① 将 “父” 设置为黑色,将 “叔叔” 设置为黑色

image-20240427121253996

② 将 “祖父” 设为 “红色”

22 的祖父就是 20,因此需要将 20 变成红色。

image-20240427121355143

然后下面有两个相对立的。

③ 如果祖父为根,再将根变回黑色

由于此时祖父 20 是根,因此此时需要变回来。

image-20240427121532026


4)当前节点不为根节点,父节点为黑色

往下,添加 17节点,比了一下它发现,17 要比 20 小,所以应该存 20 的左边。

继续跟 18 节点比较,发现比 18 小,因此存 18 的左边,当做是 18 的左子节点。

image-20240427131443535

再来对比一下规则,看看我们是否要来进行处理

如果我们添加的节点是非根,并且父节点是黑色,则不需要进行任何的操作。

因此 17 现在就放在这,不需要进行任何操作。

image-20240427131813808

接下来是同样的道理,当我们添加 2419 时,它们的父节点也都是黑色的,因此也不需要进行任何的调整。

image-20240427131917031 ----

当前节点不为根节点,父节点为红色,叔叔节点也为红色

接下来再来添加两个节点,添加这两个节点的时候就有点难了。

image-20240427132022133

首先添加 15节点,结果对比,它应该是添加在 17节点 的左边。

image-20240427133459594

这个时候它的父节点是红色的,叔叔节点也是红色的,此时就需要使用到下面深红色的处理方案。

image-20240427120555091

它需要将 父、叔、祖父... 都操作一下。

① 将 “父” 设置为黑色,将 “叔叔” 设置为黑色

② 将 “祖父” 设为 “红色”

image-20240427134114765

③ 如果祖父为非根,将祖父设置为当前节点再进行其他判断

此时祖父 18 不是根节点,所以此时我们要站在祖父节点的基础上,再次进行判断。

现在 18节点 的父节点是黑色的,此时我们需要再去表中找对应的处理方案。

可以看见,父节点是黑色的,不需要做任何操作,因此此时调整已经结束。

image-20240427134320379


5)当前节点不为根节点,父节点为红色,叔叔节点为黑色,并且当且节点是父节点的左孩子

继续往下,添加 14节点,结果对比,14节点 应该放在 15 的左边,当做是 15节点 的左子节点。

image-20240427134535645

这个时候它其实也违背了红黑规则:不能出现两个红色相连的情况

因此此时我们需要调整:14 的父节点是 ,叔叔节点是 Nil,即黑色,而且 1415 的左子节点,因此要采取的是下面深灰色的处理方案。

image-20240427134747143

前两步如下图

image-20240427135117510

第三步:以祖父为支点进行右旋

在旋转的时候是不需要考虑 Nil 叶子结点的,直接转就行了,转完后再将叶子结点补上接口。

image-20240427135409998


6)当前节点不为根节点,父节点为红色,叔叔节点为黑色,并且当且节点是父节点的右孩子

还有一种情况我们需要知道。

先将 14节点 去掉,假设我们刚刚添加的不是 14节点,而是 16节点

由于 16节点 应该添加到 15节点 的右边,当做是 15 的右子节点。

image-20240427135858540

根据表中解决方案:16 的父 15 是红色的,它的叔叔是 15 旁边的 Nil,是黑色的,并且当前节点是父的右孩子,因此需要使用下面绿色的处理方案

image-20240427135805197

作为当前节点,并左旋,再进行判断

image-20240427140130300

在判断的时候,我们需要站在 15 的基础上再来看问题了,因为在刚刚在调整的时候,它是将父节点作为当前节点

15 的父节点是红色的,叔叔节点是黑色,而且 1516 的左子节点,因此就需要采取下面深灰色的处理方案。

image-20240427134747143

将父节点变成黑色,将组父节点变成红色。

image-20240427140502202

最后以祖父17为支点进行右旋。

image-20240427140611135

旋转完毕后,整棵树就变成了下面这种情况

image-20240427140651571


要注意的是,有两个地方需要 再进行其他判断,这一点一定要注意。

image-20240427140902286

通过这两个情况知道了,有时候往红黑树中添加节点,不是调整一次就行了,有可能要调整两次,也有可能要调整三次,或者更多。


五、总结

红黑树增删改查的性能都很好。

但是有同学就觉得:处理方案这么多这么复杂,性能为什么还更好?

因为在这些处理方案中,真正浪费时间的其实是旋转,在红黑树中,更多的处理方案仅仅是修改颜色而已,修改颜色其实是不浪费时间的,它改变的仅仅是变量中记录的值而已。

并且在这些处理方案中,并不是每次都需要进行旋转的,旋转的次数跟平衡二叉树比,要少太多了,因此红黑树增删改查的性能都很好。

  • 42
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值