如果你问我红黑树的话,我可就不困了!

良心公众号

关注不迷路

红黑树作为数据结构中的难点,在大厂的面试中也是一个重点,但在日常工作中,却很少有直接使用到红黑树的场景。对于这样一个不经常接触的重难点,就成了一块比较难啃的硬骨头。

本文旨在用尽量平实的语言,详尽的绘图和清晰的脉络,从解决问题和适用场景出发,深入剖析红黑树这一数据结构。

01

从二叉树谈起

二叉树是结点的有限集合,它或者为空,或者由一个根结点及两棵互不相交的左、右子树构成,而其左、右子树又都是二叉树。

对于二叉树的结点分布,有两种特殊情况:

  • 如果一棵二叉树中的任意一层的结点个数都达到了最大值,那么这棵二叉树就称为满二叉树

  • 如果将一棵满二叉树的最底层自右至左依次去掉若干个结点,得到的二叉树称为完全二叉树

满二叉树和完全二叉树可以用下图作直观的表示:

之所以在此提到这两种特殊情况,是为了方便下文讨论二叉树的平衡性时方便引用这两个概念。

02

重要的二叉查找树

二叉查找树是一棵二叉树。它或者为空,或者同时满足以下条件:

(1)若左子树不空,则左子树中的所有元素的键值都比根结点的键值小

(2)若右子树不空,则右子树中的所有元素的键值都比根结点的键值大

(3)它的左、右子树也都是二叉查找树

我们可以发现,二叉查找树的元素键值是有序的,这使得它能够将链表插入的灵活性和有序数组查询的高效性结合起来,从而支持快速的插入、删除、查找等操作,每个操作的时间复杂度和树的高度成正比,这是非常有意义的。

但事物普遍是不完美的,二叉查找树也一样,上文已经提到了,它的插入、删除和查找等操作的时间复杂度,均与树的高度成正比。因此,决定操作效率高低的关键在于树的高度,这就带来了一些不稳定的因素。

比如,一棵二叉查找树,如果同时是一棵满二叉树或者完全二叉树,那么树的高度是最低的,此时的时间复杂度也最低,为O(logn);而如果这棵二叉查找树退化为了一条单链表,此时树的高度是最高的,时间复杂度也最高,为O(n)。

虽然退化为单链表是一种极端情况,但随着插入,删除操作的次数增加,大多数情况下,这棵二叉查找树往往会有退化为单链表的趋势,而不是成为完全二叉树的趋势。这里说到的趋势,其实就是树的高度增加的趋势。而这个趋势,就会逐渐使二叉查找树的插入,删除和查询等操作的时间复杂度从对数级别上升为线性级别。

普通的二叉查找树和退化为链表的二叉查找树,可以用下图作直观的表示:

说了这么多,其实就是为了引出一个概念,二叉树的平衡性。二叉查找树之所以有退化为链表的风险,就是因为,在它定义里提到的限制条件中,并没有保证其平衡性。因此,为了在享有二叉查找树优势的情况下,不至于承受退化为单链表的风险,需要进一步对其平衡性作出限制,从而严格控制树的高度。

上文提到的满二叉树或者完全二叉树自然是最具“平衡性”的,但如果要在对二叉查找树进行插入和删除操作时,始终保持满二叉树或者完全二叉树的特性,是非常费时、费力的。说白了,就是这个条件太过严苛,为了保证平衡性付出的代价过于沉重。

为了解决上述问题,二叉平衡查找树就应运而生!

03

优秀的二叉平衡查找树

没错,本文的主角——红黑树,就是一种二叉平衡查找树!红黑树并不是唯一的二叉平衡查找树,其实,最先被提出的二叉平衡查找树是AVL树。但由于AVL树的限制条件相对红黑树依然严苛,这导致AVL树的插入,删除操作对树的拓扑结构影响较大,而红黑树则更加稳定,这也是红黑树应用更为广泛的一个原因。

红黑树是一种二叉查找树。它具有以下特点:

(1)每个结点被染成红色或黑色

(2)根结点是黑色的

(3)如果一个结点是红色的,那么它的子结点必须是黑色的

(4)从任何一个结点出发到空结点的路径上,必须包含相同数目的黑结点

说到这里,其实最困难的才刚刚开始,那就是红黑树的具体实现。话不多说,我们结合图文,来探究如何在插入和删除操作中保证红黑树的特性。

插入操作:

根据红黑树的特点,新插入的结点(记为X)着色必定为红色(否则将会违反红黑树的性质(4)),此时会有以下三种情况:

(1) 插入结点为根结点,此时只需要将结点插入,并染色成为黑色;

(2) 插入结点的父结点为黑色,此时只需要将结点插入,无需做任何额外的处理;

(3) 插入结点的父结点为红色,此时处理起来就比较复杂了,它又可以分为三种情况:

父结点的兄弟结点是黑色的,插入结点是左子结点。此时只需要对插入结点的祖父结点右旋,然后将插入结点和其旋转后得到的兄弟结点颜色互换即可;

父结点的兄弟结点是黑色的,插入的结点是右子结点。此时只需要对插入结点的父结点左旋,便回到了情况①;

父结点的兄弟结点是红色的,此时需要将插入结点的父结点和父结点的兄弟结点都染色成为黑色,然后将插入节点的祖父结点染色成为红色,这时,我们针对插入结点的祖父结点,就可以按照上述情况进行针对处理了。

删除操作:

根据红黑树的特点,删除结点(记为X)的情况也可以分为以下三种:

(1) 删除结点是叶子结点,此时又可细分为两种情况:

① 删除的叶子结点为红色结点

删除的叶子结点为黑色结点

(2) 删除结点只有一个子结点,此时其子结点必为红色结点(否则违反了红黑树的性质(4)),那么该结点必为黑色结点(否则违反了红黑树的性质(3)),我们将该结点与其子结点的值互换,然后问题就转化为删除的是红色叶子结点,也就是情况(1)①

(3) 删除结点有两个子结点,此时将该结点与其左子结点或者右子结点的值互换,然后问题就转化为删除其左子结点或者右子结点,这是一个递归的过程,直到被删除的结点没有两个子结点,此时将转化为情况(2)或者情况(1),并最终转化为情况(1)。

因此,我们只需要细致地讨论情况(1)的解决方案即可,在此,我们不妨设被删除结点为其父结点的左子结点(同理,我们可以对称地得到被删除结点为其父结点的右子结点的结论,在此就不展开)。

对于情况(1)①,直接删除结点即可,无需做任何额外的处理。

对于情况(1)②,处理起来则比较复杂,具体可以分为以下两种情况:

(1) 被删除结点的兄弟结点是黑色结点,可以断定,被删除结点的兄弟结点不可能存在黑色子结点(根据红黑树的性质(4)),此时又可细分为四种情况:

① 被删除结点的兄弟结点为叶子结点,此时,需要将被删除结点的兄弟结点染色成为红色,并考察其父结点的染色情况,如果其父结点的染色为红色,则将其父结点染色成为黑色即可,但如果其父结点的染色为黑色,此时仍不满足红黑树的性质(4),需要递归地调整其父结点。

父结点为红色:

父结点为黑色:

被删除结点的兄弟结点有且只有一个红色右子结点,此时,需要将被删除结点的兄弟结点染色为其父结点的颜色,然后将父结点和兄弟结点的右子结点染色成为黑色,然后对父结点进行左旋即可。

被删除结点的兄弟结点有且只有一个红色左子结点,此时,只需将被删除结点的兄弟结点与兄弟结点的左子结点颜色互换,然后对兄弟结点进行右旋,就转化为(1)②这种情况。

被删除结点的兄弟结点有两个红色子结点,此时的操作和(1)②这种情况相同。

(2) 被删除结点的兄弟结点是红色结点,可以断定,被删除结点的父结点是黑色结点(根据红黑树的性质(3)),并且被删除结点的兄弟结点有两个子结点,且均为黑色结点(根据红黑树的性质(4)),此时只需交换兄弟结点与父结点的颜色,并对父结点进行右旋,即可回到情况(1)。

到这里,最复杂的红黑树的插入和删除操作就总结完毕了,其实这东西没必要为了面试死记硬背,只要理解红黑树的特点和使用场景,这些操作都是可以一步一步推导出来的。

欢迎关注【有理想的菜鸡】公众号,大家一起讨论技术,共同成长!

学习 | 工作 | 分享

????长按关注“有理想的菜鸡

只有你想不到,没有你学不到

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值