Contents
  1. 1. 二叉查找树
    1. 1.1. 查找
    2. 1.2. 插入
  2. 2. 2-3 查找树
    1. 2.1. 查找
    2. 2.2. 插入
      1. 2.2.1. 向一个父结点为2-结点的3-结点中插入新键
      2. 2.2.2. 向一个父结点为3-结点的3-结点中插入新键
      3. 2.2.3. 总结
    3. 2.3. 优点
    4. 2.4. 缺点
  3. 3. 红黑树
    1. 3.1. 性质
    2. 3.2. 对于二叉查找树
    3. 3.3. 对于2-3树
      1. 3.3.1. 替换3-结点
      2. 3.3.2. 红黑树基于2-3树的另一个定义
      3. 3.3.3. 颜色表示:
      4. 3.3.4. 旋转
      5. 3.3.5. 插入
      6. 3.3.6. 利用2-3树构造与红黑树构造过程对比

在了解红黑树以前先了解一一些基础知识。

二叉查找树

由于红黑树本质上就是一棵二叉查找树,所以在了解红黑树之前,咱们先来看下二叉查找树。

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

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

因为,一棵由n个结点,随机构造的二叉查找树的高度为lgn,所以顺理成章,一般操作的执行时间为O(lgn).(至于n个结点的二叉树高度为lgn的证明,可参考算法导论 第12章 二叉查找树 第12.4节)。

但二叉树若退化成了一棵具有n个结点的线性链后,则此些操作最坏情况运行时间为O(n)。后面我们会看到一种基于二叉查找树-红黑树,它通过一些性质使得树相对平衡,使得最终查找、插入、删除的时间复杂度最坏情况下依然为O(lgn)。

查找

在二叉查找树中查找一个键的递归算法:
如果树是空的,则查找未命中。如果被查找的键和根结点的键相等,查找命中。否则我们就在适当的子树中继续查找。如果被查找的键较小就选择左子树,较大就选择右子树。
在二叉查找树中,随着我们不断向下查找,当前结点所表示的子树的大小也在减小(理想情况下是减半)

插入

查找代码几乎和二分查找的一样简单,这种简洁性是二叉查找树的重要特性之一。而二叉查找树的另一个更重要的特性就是插入的实现难度和查找差不多。
当查找一个不存在于树中的结点并结束于一条空链接时,我们需要做的就是将链接指向一个含有被查找的键的新结点。如果被查找的键小于根结点的键,我们会继续在左子树中插入该键,否则在右子树中插入该键。

2-3 查找树

为了保证查找树的平衡性,我们需要一些灵活性,因此在这里我们允许树中的一个结点保存多个键。
2-结点:含有一个键(及值)和两条链接,左链接指向的2-3树中的键都小于该结点,右链接指向的2-3树中的键都大于该结点。
3-结点:含有两个键(及值)和三条链接,左链接指向的2-3树中的键都小于该结点,中链接指向的2-3树中的键都位于该结点的两个键之间,右链接指向的2-3树中的键都大于该结点。(2-3指的是2叉-3叉的意思)
一颗完美平衡的2-3查找树中的所有空链接到根结点的距离都是相同的

查找

要判断一个键是否在树中,我们先将它和根结点中的键比较。如果它和其中的任何一个相等,查找命中。否则我们就根据比较的结果找到指向相应区间的链接,并在其指向的子树中递归地继续查找。如果这是个空链接,查找未命中。

插入

要在2-3树中插入一个新结点,我们可以和二叉查找树一样先进行一次未命中的查找,然后把新结点挂在树的底部。但这样的话树无法保持完美平衡性。我们使用2-3树的主要原因就在于它能够在插入之后继续保持平衡。
如果未命中的查找结束于一个2-结点,我们只要把这个2-结点替换为一个3-结点,将要插入的键保存在其中即可。如果未命中的查找结束于一个3-结点,事情就要麻烦一些。

向一个父结点为2-结点的3-结点中插入新键

假设未命中的查找结束于一个3-结点,而它的父结点是一个2-结点。在这种情况下我们需要在维持树的完美平衡的前提下为新键腾出空间。
我们先像刚才一样构造一个临时的4-结点并将其分解,但此时我们不会为中键创建一个新结点,而是将其移动至原来的父结点中。

这次转换也并不影响(完美平衡的)2-3树的主要性质。树仍然是有序的,因为中键被移动到父结点中去了,树仍然是完美平衡的,插入后所有的空链接到根结点的距离仍然相同

向一个父结点为3-结点的3-结点中插入新键

假设未命中的查找结束于一个3-结点,而它的父结点是一个3-结点。
我们再次和刚才一样构造一个临时的4-结点并分解它,然后将它的中键插入它的父结点中。但父结点也是一个3-结点,因此我们再用这个中键构造一个新的临时4-结点,然后在这个结点上进行相同的变换,即分解这个父结点并将它的中键插入到它的父结点中去。
我们就这样一直向上不断分解临时的4-结点并将中键插入更高的父结点,直至遇到一个2-结点并将它替换为一个不需要继续分解的3-结点,或者是到达3-结点的根。

总结

  1. 先找插入结点,若结点有空(即2-结点),则直接插入。如结点没空(即3-结点),则插入使其临时容纳这个元素,然后分裂此结点,把中间元素移到其父结点中。对父结点亦如此处理。(中键一直往上移,直到找到空位,在此过程中没有空位就先搞个临时的,再分裂。)
  2. -3树插入算法的根本在于这些变换都是局部的:除了相关的结点和链接之外不必修改或者检查树的其他部分。每次变换中,变更的链接数量不会超过一个很小的常数。所有局部变换都不会影响整棵树的有序性和平衡性。

优点

2-3树在最坏情况下仍有较好的性能。每个操作中处理每个结点的时间都不会超过一个很小的常数,且这两个操作都只会访问一条路径上的结点,所以任何查找或者插入的成本都肯定不会超过对数级别。
完美平衡的2-3树要平展的多。例如,含有10亿个结点的一颗2-3树的高度仅在19到30之间。我们最多只需要访问30个结点就能在10亿个键中进行任意查找和插入操作。

缺点

我们需要维护两种不同类型的结点,查找和插入操作的实现需要大量的代码,而且它们所产生的额外开销可能会使算法比标准的二叉查找树更慢。
平衡一棵树的初衷是为了消除最坏情况,但我们希望这种保障所需的代码能够越少越好。

红黑树

性质

1. 每个结点要么是红的,要么是黑的。  
2. 根结点是黑的。  
3. 每个叶结点(叶结点即指树尾端NIL指针或NULL结点)是黑的。  
4. 如果一个结点是红的,那么它的俩个儿子都是黑的。  
5. 对于任一结点而言,其到叶结点树尾端NIL指针的每一条路径都包含相同数目的黑结点。

正是红黑树的这5条性质,使得一棵n个结点是红黑树始终保持了logn的高度,从而也就解释了上面我们所说的“红黑树的查找、插入、删除的时间复杂度最坏为O(log n)”这一结论的原因。
下图是一颗红黑树:

对于二叉查找树

红黑树,本质上来说就是一棵二叉查找树,但它在二叉查找树的基础上增加了着色和相关的性质使得红黑树相对平衡,从而保证了红黑树的查找、插入、删除的时间复杂度最坏为O(log n)。

对于2-3树

红黑树就是用红链接表示3-结点的2-3树。那么红黑树的插入、构造就可转化为2-3树的问题,即:在脑中用2-3树来操作,得到结果,再把结果中的3-结点转化为红链接即可。而2-3树的插入,前面已有详细图文,实际也很简单:有空则插,没空硬插,再分裂。 这样,我们就不用记那么复杂且让人头疼的红黑树插入旋转的各种情况了。只要清楚2-3树的插入方式即可。 下面图文详细演示。
红黑树是对2-3查找树的改进,它能用一种统一的方式完成所有变换。
红黑树背后的思想是用标准的二叉查找树(完全由2-结点构成)和一些额外的信息(替换3-结点)来表示2-3树。

替换3-结点

我们将树中的链接分为两种类型:红链接将两个2-结点连接起来构成一个3-结点,黑链接则是2-3树中的普通链接。确切地说,我们将3-结点表示为由一条左斜的红色链接相连的两个2-结点。
这种表示法的一个优点是,我们无需修改就可以直接使用标准二叉查找树的get()方法。对于任意的2-3树,只要对结点进行转换,我们都可以立即派生出一颗对应的二叉查找树。我们将用这种方式表示2-3树的二叉查找树称为红黑树。

红黑树基于2-3树的另一个定义

红黑树的另一种定义是满足下列条件的二叉查找树:

  1. 红链接均为左链接。
  2. 没有任何一个结点同时和两条红链接相连。
  3. 该树是完美黑色平衡的,即任意空链接到根结点的路径上的黑链接数量相同。

如果我们将一颗红黑树中的红链接画平,那么所有的空链接到根结点的距离都将是相同的。如果我们将由红链接相连的结点合并,得到的就是一颗2-3树。
相反,如果将一颗2-3树中的3-结点画作由红色左链接相连的两个2-结点,那么不会存在能够和两条红链接相连的结点,且树必然是完美平衡的。

2-3树的深度很小,平衡性好,效率高,但是其有两种不同的结点,实际代码实现比较复杂。而红黑树:

  • 红链接表示2-3树中另类的3-结点
  • 统一了树中的结点类型,使代码实现简单化,又不破坏其高效性。


无论我们用何种方式去定义它们,红黑树都既是二叉查找树,也是2-3树。

颜色表示:

因为每个结点都只会有一条指向自己的链接(从它的父结点指向它),我们将链接的颜色保存在表示结点的Node数据类型的布尔变量color中(若指向它的链接是红色的,那么该变量为true,黑色则为false)。
当我们提到一个结点颜色时,我们指的是指向该结点的链接的颜色。

旋转

在我们实现的某些操作中可能会出现红色右链接或者两条连续的红链接,但在操作完成前这些情况都会被小心地旋转并修复。
(我们在这里不讨论旋转的几种情况,把红黑树看做2-3树,自然可以得到正确的旋转后结果)

插入

在插入时我们可以使用旋转操作帮助我们保证2-3树和红黑树之间的一一对应关系,因为旋转操作可以保持红黑树的两个重要性质:有序性完美平衡性

利用2-3树构造与红黑树构造过程对比

参考来源:

  1. https://github.com/julycoding/The-Art-Of-Programming-By-July/blob/master/ebook/zh/03.01.md
  2. http://blog.csdn.net/yang_yulei/article/details/26066409
Contents
  1. 1. 二叉查找树
    1. 1.1. 查找
    2. 1.2. 插入
  2. 2. 2-3 查找树
    1. 2.1. 查找
    2. 2.2. 插入
      1. 2.2.1. 向一个父结点为2-结点的3-结点中插入新键
      2. 2.2.2. 向一个父结点为3-结点的3-结点中插入新键
      3. 2.2.3. 总结
    3. 2.3. 优点
    4. 2.4. 缺点
  3. 3. 红黑树
    1. 3.1. 性质
    2. 3.2. 对于二叉查找树
    3. 3.3. 对于2-3树
      1. 3.3.1. 替换3-结点
      2. 3.3.2. 红黑树基于2-3树的另一个定义
      3. 3.3.3. 颜色表示:
      4. 3.3.4. 旋转
      5. 3.3.5. 插入
      6. 3.3.6. 利用2-3树构造与红黑树构造过程对比