为什么要平衡
在上一节中,我们了解了 二叉搜索树
具有较稳定和较高的插入搜索效率。但是在某些极端情况下, 它的效率也会退化到 链表
的地步。
比如以上一堆数据,按照插入顺序的不同,所构建二叉搜索树的结构也会不一样,如下图所示:
什么是树的平衡
虽说是只要平衡就可以了,但是我们也不能随随便便胡搞瞎搞的只是将这棵树的高度降低就可以了,正如下图用一个压片机将图b压成一个完全二叉树一样
上图得到的处理结果虽然得到了一棵看似完美的完全二叉树,但是问题在于得到的处理结果已经不再是一棵二叉搜索树
了,也就是说它不再符合二叉搜索树的结构特点(比某结点值小的结点都在此结点的左子树,比某结点值大的结点都在此结点的右子树),也就失去了二叉搜索树的各种优势。
平衡原则
一个合格的平衡操作应该保持以下两点原则:
经过平衡操作之后的树,仍然保持二叉搜索树结构特点。便于之后快速查询操作
在符合1的前提下,尽量压缩树的高度;让一个失去平衡的树重新看起来左右均衡一些
平衡实现
那如何实现符合如上两点要求的平衡操作呢? 业界普遍比较认可的一种稳定、高效、完整的平衡操作就是 红黑树 的平衡操作, 它主要是由以下两个操作组合而成:
旋转(Rotation)
重新着色(Re-Color)
红黑树
试想我们将一组数据 [10, 9, 8, 7, 6, 5, 4, 3, 2 , 1] 依次插入到一棵二叉搜索树中。如果按照普通二叉搜索树的规则,当将数据8插入到树中之后的结构会如下所示
Note:首选我们必须牢牢记住!在一棵 `红黑树` 中永远都不允许有以下图中的两种结构(姑且称之为3结棍式结构)存在
可以看出上图中的两种情况都已经处于一种比较失控状态了,如果再任其发展下去就是文章开始时所说的那种极端情况了。当一棵红黑树
出现这种情况时,我们就说检测到了一个平衡违规(balance-violation)。
旋转(Rotation)
对于平衡违规(balance-violation),红黑树是绝对不能姑息的,需要及时矫正。对于如何矫正,我们以 图c 来举例当遇到这种情况时,该如何操作。
首先我们需要保证在平衡之后,图c中的结构仍然是 二叉搜索树,因此我们不能单纯的将 结点8 替换到 图中null 的位置。因此我们需要用到一种被称为 右旋转 的操作,所谓右旋转就是围绕某个结点的右旋,比如下图围绕结点8进行右旋:
对,没有错!树的高度减少了,但是根结点
确实发生了改变!从之前的 10
换成了 9
。虽然是这样,但是这棵树依然还保持着 二叉搜索树
的结构特点。并且经过旋转之后的,后续的查询操作会更加高效。
问题不大,并没有出现 3结棍式结构
,并且从视觉效果上来看,树的左右两边还算是比较平衡,左边的高度只比右边高度高1,所以不需要做平衡操作。
如图所示,因为 6 < 7,所以结点6
本应该是结点7
的左子结点。但是也正如图中所指出的,如果将6
添加为7
的子结点,会形成一个3结棍式结构
,也就造成了平衡违规(balance-violation)。所以我们要在这个3结棍式结构
上进行局部右旋转
操作,如下所示:
经过旋转操作之后,树的高度变为3,并且左右两边子树的高度相差为1,所以后续二叉搜索的时间复杂度还是维持在O(log n)级别。
上图中并没有出现3结棍式结构,所以并没有明显的平衡违规(balance-violation)。所以不需要做旋转操作。
但是上图中的树形结构还是存在一个问题,根结点的左子树的高度比根结点右子树高度相差已经大于1。从肉眼上也能看出,树的左边已经有了太多的结点,而右边只有一个结点10。直觉也告诉我们是不是应该做点什么,不能任由事态继续这么发展下去啊,否则眼瞅着就要失控了!
那具体应该做出如何改变?以及是基于什么原则做出的改变呢?这个时候就需要平衡操作的第二步了–重新着色(Re-Color)
重新着色(Re-Color)
在红黑树的 Wiki 页面中有介绍,一棵红黑树除了满足二叉搜索树的特征之外,还需要满足以下几点额外要求, 总结一下就是如下5项基本原则:
红黑树5项基本原则
红黑树中的每一个结点中附带颜色属性,要么是红色或黑色。当插入一个结点时,默认颜色为红色
根结点必须是黑色。
所有叶子都是黑色(叶子是NIL节点)
每个红色节点必须有两个黑色的子节点。(或者是从每个叶子到根的所有路径上不能有两个连续的红色节点。)
从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。
注意: NIL不等同于null, 所谓NIL结点是结点中的value为null。上文中所有的图片都默认没有画出叶子结点,比如下图
只是为了画图更加方便,我默认将所有叶子结点NIL结点
给隐藏掉了。读者心中需要明白其实树的高度应该再加一层叶子节点的高度。
接下来就按照这5条规则,将插当前插入结点5
之后的二叉搜索树Re-color(重新着色),显示如下效果
很显然5、6、8结点的颜色违反了4和5两条原则。并且被插入结点 5 的父结点和叔结点都是红色,对于这种情况,我们需要作如下操作:
- 将被插入的结点 5 的父结点 6 和叔结点 8 的颜色由红色改为黑色
- 将被插入结点 5 的祖父结点 7 由黑色改为红色
修改之后的结果如下所示,这样插入结点 5
的操作就算完成了。:
插入结点4
,并默认颜色为红色,显示如下:
相信不用我提醒,你也应该能看到,上图中既有`3结棍式结构`,也有违反红黑树5项基本原则的情况,因此既需要重新着色,也需要旋转操作。当这两种操作都需要执行的时候,我们需要先执行重新着色操作(ReColor),后执行旋转(Rotatioin)操作,结果如下:
添加结点3
就有点特殊了,首先来看下默认添加结点3
之后的显示效果
情况跟插入结点`5`时差不多,被插入结点 3 的父结点和叔结点都是红色,因此还是需要修改祖父结点为红色,将父结点叔结点改为黑色,结果如下:
正如图中标出的指示一样,修改颜色之后,又引发的连锁反应:5、7两个相连结点都为红色,这就违反了红黑树5项基本原则中的第4条–不能有连个相连的结点颜色都为红色。
对于这样的情况,仅仅一步重新着色已经不能保证一步正确的平衡操作了,因此需要再进行一步旋转操作,具体旋转的目标就是被重新着色为红色的结点5的父结点7(有点绕,总之就是当前被插入结点3的曾祖父结点)进行旋转(Rotation)操作,操作之后结果如下:
可以发现,根节点重新换成了结点7
, 但是问题是根结点7
因为向右旋转多出来一条腿–结点8
, 对于这种情况(被右旋的结点如果右子树不为空,需要将其右子树重新替换给新的右子树),也就是说我们需要重新将结点8
插入到结点9
中, 最终结果如下:
从肉眼上看,二叉搜索树又变得左右比较对称了起来!后续添加结点2、1也是按照这个思路,基本没有区别。
到这里右旋转所遇到的问题基本就能涵盖全了, 对于左旋转也是类似的操作。我们这里只是带领大家了解红黑树执行的原理,了解这些就已经足够了。如果有对左旋转以及删除结点也感兴趣的童鞋可以参考 https://en.wikipedia.org/wiki/Red–black_tree
文章总结
这一节我们首先讲了对于二叉搜索树来说,为什么需要做平衡的操作或者处理。
接下来讲了红黑树中实现平衡处理的两种操作:Rotation + ReColor。
最后我们讲了红黑树为什么以及何时进行Rotation和ReColor操作。
我们可以试着再去理解一下,红黑树的整个平衡过程就是一个迭代过程,在这个不断迭代的过程当中,二叉搜索树的结构特征不会改变,但是会尽量将一个处于中间值的结点移动到根结点的位置,这样就能保证分布在根结点两边的子结点数量比较均衡,从而树的高度也会被尽量的压缩。而这一切操作都来源于 红黑树 的定义
1、红黑树中的每一个结点中附带颜色属性,要么是红色或黑色。当插入一个结点时,默认颜色为红色
2、 根结点必须是黑色。
3、所有叶子都是黑色(叶子是NIL节点)
4、每个红色节点必须有两个黑色的子节点。(或者是从每个叶子到根的所有路径上不能有两个连续的红色节点。)
5、从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。
---------------------
作者:Danny_姜
来源:CSDN
原文:https://blog.csdn.net/zxm317122667/article/details/85317763
版权声明:本文为博主原创文章,转载请附上博文链接!