写在前面:
之前在研究HashMap的时候就听说过红黑树的大名,畏于其复杂性没敢深入研究,被某些人好一顿嘲笑,这次决心搞点儿复杂的东西,就直接从红黑树下手了.
我研究了几天,发现网上资料不少,但就是让人云里雾里的,总觉着有些问题没有看明白.后来发现是因为大部分up主讲的不够透彻,遇到他们觉得理所当然的地方就略过去了,但就是这理所当然的地方,总是让我想不通!现在我基本想通了,就整一篇,供大家参考.
红黑树介绍
红黑树的定义:
红黑树(Red Black Tree)是一种自平衡二叉查找树. --百度百科
解释:
1. 红黑树是一种树.
不知道什么是树的请出门左拐.
2. 红黑树是一种二叉查找树.
所谓二叉是每个节点拥有的子节点数小于等于两个;查找树是指父节点的值大于等于左子树(注意不是左子节点)中的任意一个值,而小于等于右子树(注意不是右子节点)中的任意一个值.
3. 红黑树是一种自平衡二叉查找树.
自平衡就是在插入和删除节点时,红黑树会通过某种方式(后边会介绍)保证从根节点到每个子节点的距离不会超过(可以等于)两倍.有人说这叫黑色完美平衡,
引申:
平衡二叉树是指根节点到每个末端子节点的距离差值最大不会超过1,而红黑树的根节点到每个末端子节点的距离差值不会超过两倍,所以严格来说红黑树不属于平衡二叉树,以至于有的人这么说:红黑树是一种平衡二叉树,但它不是严格的平衡,而是不严格的平衡....好吧,怎么理解看你自己了,知道是怎么回事儿就行.
红黑树是基本平衡的,至少它的查询复杂度与平衡二叉树基本相同.(关于这些复杂度的内容我会另写一篇,本文暂不深究.)
红黑树性质:
1. 节点是红色或黑色的;(被称为红黑树的根本)
2. 根节点必须是黑色;(为什么?下边我会详细解释)
3. 每个叶子节点都是黑色的空节点(NIL节点);(这是最容易让人误解的一条,也是最重要的一条,下边我会详细解释)
4. 每个红色节点的子节点都是黑色的;(可以这么理解:每个红色节点的父节点和子节点都不能是红色的)
5. 从任意节点到其每个叶子节点的所有路径都包含相同数目的黑色节点.(这一条跟第三条联系在一起更容易误解,我这就详细解释)
规则释疑:
-
NIL就是说什么也没有,NIL节点就是说这个节点不存在,但你可以想象有这么个节点.我们可以这么理解第三条和第五条规则:一.NIL节点存在且是黑色的;二.对所有节点而言(包括根节点\普通节点\叶子节点,但NIL节点除外),如果它没有左子节点,那么你就认为它的左子节点是个NIL节点,如果它没有右子节点,那么你就认为它的右子节点是个NIL节点,如果左子节点和右子节点都没有,那么它的左子节点和右子节点都是NIL节点;三.根节点到每个NIL节点的路径中包含的黑色节点数目相同.
-
注意: 任一节点不能只有一个黑色子节点(就是说左子节点为黑色节点,然后右子节点为空的话不可以(右子节点为红色的话是可以的),或右子节点为黑色节点,然后左子节点为空的话不可以(左子节点为红色的话是可以的)),也可以这么说,红色节点只能有0个或两个子节点,黑色节点可以有0个\1个\2个子节点,但是当它只有一个子节点时,该子节点必须是红色节点
上边这两句话万万要理解,否则后边肯定不懂!!为了加深印象,我画几张图.
图一
图一有问题,但并不是因为它是全黑的,而是因为它的根节点到每个NIL节点所经过的黑色节点数不同.
图二
图二没有问题
图三
图三有问题,因为S节点到SL的NIL节点和到P的NIL节点所经过的黑色节点的数量不一样。
图四
图四有问题,因为S的右子节点(我没画出来)是个NIL节点,根节点到这个NIL节点只经过一个黑色节点,然而到P节点或SL节点的NIL节点却经过两个黑色节点.(这个一定要理解)
根节点为什么必须是黑色?
假设一棵树只有一个红色的根节点(如图五所示),那么再插入一个节点后,该节点必须是黑色(规则4),那么从根节点到每个NIL节点的路径中包含的黑色节点数目就不相同了(如图六所示),然后就必须自平衡(后边会介绍如何自平衡),那么自平衡后的结果还是根节点变为了黑色(如图七所示),所以红黑树的根节点不如一开始就是黑色的(反正你插入第二个节点之后它就会变成黑色).
图五
图六
图七
注意:
1. 黑色节点的父节点\子节点\兄弟节点既可以是黑色的,也可以是红色的;(红色节点的限制比较多)
2. 一棵树可以全是黑色的;(这种情况就是一颗满二叉树)
3. 在一棵树的所有路径中,最长的路径一定是红色节点最多的那条,最短的路径一定是红色节点最少的那条;(因为每条路径上的黑色节点数量一样呀)
红黑树查找
查找步骤:
-
查找根节点,将根节点设为当前节点;
-
如果当前节点为空,返回null;
-
如果当前节点不为空,比较key是否相等,如果相等则返回对应的值,;
-
如果当前节点大于要查找的节点,那么将当前节点的左子节点设为当前节点,然后从步骤2开始重新执行;
-
如果当前节点小,那么将当前节点的右子节点设为当前节点,然后从步骤2开始重新执行.
流程图如下:
注意:
1. 这是个递归,退出条件在步骤2或步骤3;
2. 当这棵树是空树或不存在对应的key时都会返回null
红黑树的插入
自平衡方式
在学习红黑树的插入问题之前,要先知道它是如何自平衡的.
自平衡的方式无外乎三种:左旋\右旋\变色,变色是最好理解的,就是红色变为黑色或黑色变为红色,至于左旋和右旋可以通过下面两个动图来深入理解
左旋(以S为支点左旋) 右旋(以E为支点右旋)
只要有E节点\S节点就能发生旋转,剩下的三个节点有没有的都不重要.
自平衡有几个需要遵守的原则:
1. 不到万不得已,不要增加减少黑色节点层数(会导致路径中黑色节点层数增加或减少,进而导致所有路径都需要更改)
2. 不要轻易改动最高层节点(就是旋转后位于原祖父节点位置的节点)的颜色(如果祖父节点的父节点的颜色是黑色还好,如果是红色,需要递归去修改祖父节点的父节点,甚至祖父节点的祖父节点,甚至要一直改到根节点)
红黑树的插入拢共分两步:
-
查找插入节点的父节点
-
插入节点并自平衡
之前已经聊过如何查找节点(注意这里是查找父节点,与查找节点大同小异),所以这里着重介绍如何自平衡.
自平衡要分情况讨论,如下: