一、前言
这几天想学一学红黑树这种数据结构,于是上网找了很多篇博客,初看吓了一跳,红黑树竟然如此复杂。连续看了几篇博客后,算是对红黑树有了一些了解,但是它的原理却并不是特别理解。网上的博客,千篇一律的都是在叙述红黑树的操作,如何插入节点、删除节点,旋转、变色等,只关注如何正确构建一棵红黑树,但是却很少提及为什么这么做。这篇博客我就来记录一些我所知道的红黑树中比较重要的东西,以及谈一谈我的理解。
我不会描述红黑树的具体实现,因为阅读红黑树具体实现的过程中,我发现这真的不是很重要,没有太大的意义(这绝对不是自我安慰⊙﹏⊙∥),真正重要的是红黑树的思想。如果想要了解红黑树的具体实现,建议阅读这篇文章:https://mp.weixin.qq.com/s/hGHJonK999TAVJakPDNAkg
二、正文
2.1 二叉搜索树和平衡二叉树
在谈红黑树之前,先来说一说二叉搜索树以及平衡二叉树,因为平衡二叉树是为了弥补二叉搜索树而发明出来的,而红黑树又是为了弥补平衡二叉树。
(1)二叉搜索树
二叉搜索树比较简单,它是一棵二叉树,而且满足这样一个性质:对于树上的每一个节点,它左子树上的节点的值都比它小,而右子树上的节点的值都比它大。如下,就是一棵二叉搜索树:
当我们需要往一棵二叉搜索树中插入节点时,只需要从根节点开始,依次比较,若比根节点小,则向左走,继续和左子节点比较,反之继续和右子节点比较,以此类推。这样的操作其实就和二分差不多,在节点值分布均匀的情况下,也就是树的结构如上所示时,查找的时间复杂度为O(logN)
,因为二叉搜索树的查询完全复杂度依赖于树的深度。所以,它有一个很大的缺陷,就是可能出现如下情况:
我们每次插入的节点都比最小值要小,于是最终所有的节点就连成了一条链,此时深度为N,查找的时间复杂度就是O(N)了。为了解决这个问题,于是又有了平衡二叉树。关于二叉搜索树,可以参考这个博客:https://www.jianshu.com/p/ff4b93b088eb。
(2)平衡二叉树
平衡二叉树也是一棵二叉搜索树,但是它还有一个性质,就是这棵树上的每一个节点,其左子树和右子树的高度差都不超过一。比如图一中的二叉树,它就时一棵平衡二叉树,而下面这张图中的树就不是平衡二叉树,因为节点9
和节点5
都不平衡,它们的左右子树的高度差都是2
。
当发生不平衡的情况时,可以通过左旋和右旋的方式,在不改变二叉搜索树的性质的前提下,将树调整为平衡状态,这就是平衡二叉树。对于左旋和右旋的具体实现,我这里就不描述了,想要了解可以参考这篇博客:https://www.jianshu.com/p/655d83f9ba7b。
2.2 有了二叉搜索树和平衡二叉树,为什么还要红黑树
这个问题是一个面试题,下面我就来简单叙述一下。首先,二叉搜索树在极端情况下会变成一个链表,从而导致查询的时间复杂度大大提高,变成线性复杂度,而平衡二叉树正是为了解决这个问题才被发明出来。但是,平衡二叉树也有一定的局限。对于平衡二叉树来说,每个节点的子树的高度差不能超过1
,一旦出现违反的情况,就需要进行左旋或者右旋操作,从而调整二叉树的高度。但是,在插入和删除节点的过程中,平衡度超过1
是经常发生的事情,这也就导致了调整操作频繁地发生,从而提高时间复杂度。也就是说,这种追求绝对平衡的行为,并不是一个好的做法。
红黑树的出现正是为了解决平衡二叉树的问题,它是平衡二叉树的变形,或者说升级。红黑树并不追求绝对的平衡,它实现的是近似的平衡,这样破坏规则导致需要调整的情况就会大大减少,而且每次也只需要少量的调整即可恢复。也就是说,他在平衡和效率之间做了一个折中。但是统计发现,这种折中对查找的效率并没有多大的降低,这也就导致了红黑树在大多数情况下要优于平衡二叉树。而红黑树的这种折中,依靠的就是它五条性质,或者说五条约束来实现。
2.3 红黑树的五大性质
这里先放出红黑树的五大性质:
- 树中的所有节点,要么是红色,要么是黑色;
- 树的根节点是黑色;
- 树中的叶子节点是黑色;(注意:这里的叶子节点不是指没有子节点的节点,而是指的空节点,比如某个节点没有左子节点,那这个不存在的左子节点其实就是这里说的叶子节点)
- 红色节点的子节点一定是黑色,也就是说没有两个连在一起的红色节点;
- 每个节点到任意一个叶子节点,经过的黑色节点数量是相等的;
这五条性质单独分开来看都很好理解,但是放在一起就有点迷惑了,为什么是这五条性质,它们之间有什么关系?关系肯定是有的,发明红黑树的专家,经过大量的数学推导,理论验证,才组合出这五条性质,它们相辅相成,共同维护了红黑树的结构。就像机器人三大准则一样,相辅相成,完全地约束了机器人的行为。这里就来简单描述几条红黑树的定理,它们都是由上述结论推导而来。
2.4 红黑树的定理
(1)红黑树从根节点到叶子节点的最长路径,不会大于最短路径的两倍
这条定理是怎么来的呢?它是通过上面的定理推导而来。我们看上述性质5
可知,从根节点到任意一个叶子节点,经过的黑色节点数目是相同的。而性质4
又规定,不能有两个红色节点连在一起。于是我们这样考虑,假设从根节点到每个叶子节点,都是经过3
个黑色节点,那么怎样才能让路径尽可能长?答案当然是尽量在黑色节点中插入红色节点,用红色节点充数。但是由于性质4
,所以红色节点不能多放。于是,理论上的最长路径,一定是一个黑节点,连着一个红节点,再连着一个黑节点,以此类推。我们之前假设每条路径都是3
个黑节点,再加上性质2
说根节点一定是黑色,所以从根节点到子节点最多只能有6
个节点,三黑三红交叉连接。那理论上的最短路径又是如何?当然是不包含任何一个红色节点,也就是只有3
个黑节点。也就是说,理论上,最长路径,最多是最短路径的两倍。这个结论就限制了红黑树的最大平衡差,也就保证了不会出现图二这种情况。
(2)一棵含有n个节点的红黑树的高度至多为2log(n+1)
这个结论是经过一系列数学推导得出的,比较复杂,我也没有仔细研究过,想要了解的可以看看这篇博客:https://www.cnblogs.com/skywang12345/p/3245399.html#a2。
从这个结论可以看出什么?红黑树也是一个二叉搜索树,而二叉搜索树的查找次数取决于树的深度。此结论说明红黑树的高度至多为2log(n+1)
,也就是说红黑树的查找效率也是O(logn)
级别的,和平衡二叉树近似,再加上红黑树的调整平衡的次数相对较少,所以总体来说,红黑树要优于平衡二叉树。
三、总结
最后我忍不住吐槽一下,我看了大量红黑树的讲解博客,都是千篇一律的在描述如何操作构建一棵红黑树,如何操作能够不违反红黑树的这五条性质,一旦讲完操作就戛然而止。在学习红黑树时,我硬着头皮把红黑树的操作看完,才发现这些操作根本没有太大的意义,它们只不过是列举操作红黑树的各种情况,告诉你什么时候应该怎么做而已,而你只能背下这些操作,但是完全不能理解这些操作有什么意义,只知道它们唯一的作用就是维持这五大性质。仿佛将红黑树的操作实现强行记下,写出代码,就是学会了红黑树,理解了红黑树。可是实际上,这五条性质只是结论,只是他人研究的最终产物罢了,而红黑树仅仅是在使用这些结论。如果只是学会了红黑树的实现方式,仅仅只是在使用这些结论,和调API
有什么区别。但是,网上的博客都是千篇一律,偏偏标题取得是一个比一个厉害,什么《学会红黑树这一篇就够了》,《十分钟彻底理解红黑树》,《看完这篇博客,再也不怕面试管问红黑树了》......这些标题的博客数不胜数,但是内容却都是在介绍操作,极少叙述思想。一想到我花了大量时间,最后发现自己看的都是些意义不大的实现细节,就感觉对不起自己。
我个人认为,学习算法和数据结构,真正重要的是理解它的思想,而不是如何做。说句实话,如果不是做算法相关的工作,工作中真的需要我们自己写红黑树吗?可以说基本上不可能用到。当然,不是说研究具体实现是一种错误的行为,研究算法和数据结构的实现能够锻炼自己的编码能力,是有好处的。但是大部分人是为了学习数据结构本身,如果只学会了写代码,不知道原理,那就有点本末倒置了,而且很快就会忘记。