数据结构之-深入理解红黑树

概述

本文将会透彻理解什么是红黑树,有什么特点、优点与缺点,与其它树结构(二叉查找树、平衡二叉树、2-3-4树)有什么区别和联系。写作本文的目的旨在加深自己的理解,文中许多内容参考了网络上的文章并根据自己的理解进行了整理。

第一部分:什么是红黑树

红黑树(英语:Red–black tree),一种二叉查找树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。红黑树,作为一棵二叉查找树,满足二叉查找树的一般性质。下面,来了解下 二叉查找树的一般性质。

二叉查找树,也称有序二叉树(ordered binary tree),或已排序二叉树(sorted binary tree),是指一棵空树或者具有下列性质的二叉树:

  • 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 任意节点的左、右子树也分别为二叉查找树。
  • 没有键值相等的节点(no duplicate nodes)。

红黑树通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。因此红黑树也是B树(Balance Tree)的一种,红黑树相对于B树来说,牺牲了部分平衡性以换取插入/删除操作时少量的旋转操作,整体来说性能要优于B树(不容易不平衡,减少了再平衡操作)。下面是平衡二叉树的性质:

平衡二叉树的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值(平衡因子)不超过1。也就是说平衡二叉树每个节点的平衡因子只可能是-1、0和1。

在二叉查找树强制一般要求以外,红黑树增加了如下的额外要求:

  1. 节点是红色或黑色。
  2. 根是黑色。
  3. 叶子节点都是黑色(叶子是NIL节点)。
  4. 叶子节点不包含数据。
  5. 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
  6. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。

上述条件使红黑树具有如下特点:最深的的叶子节点的深度不会大于两倍的最浅叶子节点的深度。所以,红黑树总是半平衡的。 

为什么会这样?第六个属性保证了拥有红色节点的路径要比纯黑色节点的路径长。所以最短的路径 ,是只包含黑色节点的路径。第五个属性保证了没有相连的红色节点,所以最长的路径,是红黑交替的路径。

红黑树的特点、优点与缺点

  1. 红黑树是二叉查找树,所以红黑树具有二叉查找树的所有特性。
  2. 红黑树是半平衡的,所以红黑树是B树的一种但不严格符合B树的平衡条件,即平衡因子的绝对值不大于1。
  3. 红黑是用非严格的平衡来换取增删节点时候旋转次数的降低,因此在增加或者删除节点的时候,根据不同情况,B树旋转的次数比红黑树要多。所以红黑树的插入、删除效率更高!!!
  4. 由于B树高度平衡(插入/删除更容易引起不平衡),因此B树的search效率更高

第二部分:为什么说红黑树与2-3-4树是等价的数据结构

先说一下什么是2-3-4树?2-3-4树是四阶的 B树(Balance Tree),但不是二叉树。它的结构有以下限制:

  • 所有叶子节点都拥有相同的深度。
  • 节点只能是 2-节点、3-节点、4-节点之一。
    2-节点:包含 1 个元素的节点,有 2 个子节点;
    3-节点:包含 2 个元素的节点,有 3 个子节点;
    4-节点:包含 3 个元素的节点,有 4 个子节点;
  • 元素始终保持排序顺序,整体上保持二叉查找树的性质,即父结点大于左子结点,小于右子结点;而且结点有多个元素时,每个元素必须大于它左边的和它的左子树中元素。

  一棵树在查找看来变得不平衡是因为子树的高度相差很大。二叉树为什么会这么容易变得不平衡,很简单,因为它只有二叉,左右均有50%的概率,那么插入N个节点全部都是左节点或者右节点的概率就是50%的N次方,如果是8叉树,那么这个概率就是12.5%的N次方。

  树的宽度越大,高度越小,这样查询起来越快。 我们也不能一下子就上256叉树,即使那样在海量节点情况下也抗不住,因此这种盲目宽度换高度的方案没有可扩展性。我们需要找出一种动态的机制,让一棵树动态调整保持平衡。

2-3树的平衡变换

  如果是二叉树,那么你插入一个节点,你只有最多1次机会保持子树的高度不变,如果是一个三叉树,那么就有2次机会。现在开始,我们为二叉树添了一叉,变成了三叉树。

   二叉树的时候,一个节点有两个分支,三叉树的时候,有三个分支。一个点可以将区间分为两个部分区域,要想将一个区间分为三个部分区域,就需要两个点,因此三叉的情形下,节点存储的是两个点而不是一个,如右下图所示:

  现在考虑插入一个新节点,这个2-3树怎么保持平衡。非常简单,我们知道,插入的位置一定是叶子,假设当前的树是平衡的,现在分两种情况:

1).插入的新叶子节点的父节点是一个二叉节点

这种情况最简单,二叉节点变三叉节点即可,如下图所示:

2).插入的新叶子节点的父节点是一个三叉节点 

  这种情况比较复杂。树总是要长高的,保持平衡的方式就是同时长高,而这是不可能的,插入一个节点只能让该节点所在的子树长高。然而,如果能将这个信息上升到根部,在根部长高,就实现了“同时长高”!

还是循着上面的那个思路,我们继续增加树叉的数量,我们把它增加到4!新节点的插入如下图所示:

 很遗憾,没有完成任务,但是最终我们提出了两个问题,只要解决了这两个问题,所有问题就解决了。解决这两个问题,无疑都要牵扯到节点P的父节点以及再往上的节点,有两种可能:

可能性1:P的父节点PP是一个二叉节点

这个太爽,我们直接把P以及它的子树全部提到PP节点即可,类似B插入的情景,如下图所示:

问题2解决。

可能性2:P的父节点PP是一个三叉节点 

  这就有点不好办了,不管怎样先把P节点以及其子节点全部上提到PP,保持最底部的平衡性,这样就可以递归解决了,此时我们又一次遇到了往一个三叉节点里面插入子节点的问题了,为了不增加树高,唯一的方式就是膨胀成一个四叉节点-宽度换高度。如下图所示:

最后,我们发现,在递归的过程中,要么碰到了P..P是个二叉节点,此时按照问题2的解决方式将当前节点的值直接提到P...P中,其子树降低一个高度,抵消增加的高度,平衡保持,递归结束,要么递归到了根节点,此时只需要一个分裂操作即可完美结束!

演进到红黑树 

  很显然,通过上面的描述,我们似乎找到了一个使树保持平衡的方案,而且是相当完美的平衡!核心就是宽度和高度之间的博弈。我们总是可以用一个宽度抵消一层高度,整个过程就是一次或者多次的一加一减,最终的结果还是0!
       然而,这也不再是二叉树了,有的节点变成了三叉,并且保存了两个值,该两个值将区间分割成了三部分,是为三叉!因此在使用上就不如二叉树方便,比较操作复杂化了。事实上,将三叉节点处理成二叉节点,这棵树就成了红黑树!怎么处理呢?很简单!如下图所示:

看到了吧,红色节点就是从2-3树中分出来的,为了维持一棵二叉树而不是2-3树,必须将三叉节点变成二叉节点,这是一个宽度换高度得回退,即高度换宽度,当然代价就是不再完美平衡。 

按照以上的这个变换,你自己试试看,可以变出两个连续的红节点吗?NO!下面我们来看一下它的最坏情况是什么? 还是以2-3树分析,如果在一棵2-3树中,最左边路径上的节点全部是三叉节点,而最右边路径上的节点都是二叉节点,那么把它变换成二叉红黑树之后,就会发现最左边的路径上是红黑间隔的节点,而最右边的路径上全部是黑节点,它们的高度差接近2倍。出现这样的情况是令人悲哀的,但是也是极低概率的。

  事实上, 2-3-4树的每一个结点都对应红黑树的一种结构,所以每一棵 2-3-4树 也都对应一棵红黑树,下图是 2-3-4树不同结点与红黑树子树的对应。红黑树的所有操作包括旋转等,都可以映射到2-3树中。

å¾çæè¿°

 

第三部分:操作红黑树

参考内容:

链接:https://blog.csdn.net/v_JULY_v/article/details/6105630
链接:https://www.imooc.com/article/22520

猜你喜欢

转载自blog.csdn.net/u011298001/article/details/83302676