红黑树(转)

咱们使用符号表这个词来描述一张抽象的表格,咱们会将信息(值)存储在其中,而后按照指定的键来搜索并获取这些信息。键和值的具体意义取决于不一样的应用。面试

符号表中可能会保存不少键和不少信息,所以实现一张高效的符号表也是一项颇有挑战性的任务。算法

咱们会用三种经典的数据类型来实现高效的符号表:二叉查找数、红黑树、散列表。数组

 

  1. 二分查找


咱们使用有序数组存储键,经典的二分查找可以根据数组的索引大大减小每次查找所需的比较次数。数据结构

在查找时,咱们先将被查找的键和子数组的中间键比较。若是被查找的键小于中间键,咱们就在左子数组中继续查找,若是大于咱们就在右子数组中继续查找,不然中间键就是咱们要找的键。性能

 

通常状况下二分查找都比顺序查找快的多,它也是众多实际应用程序的最佳选择。对于一个静态表(不容许插入)来讲,将其在初始化时就排序是值得的。spa

 

固然,二分查找也不适合不少应用。现代应用须要同时可以支持高效的查找和插入两种操做的符号表实现。也就是说,咱们须要在构造庞大的符号表的同时可以任意插入(也许还有删除)键值对,同时也要可以完成查找操做。.net

 

要支持高效的插入操做,咱们彷佛须要一种链式结构。当单连接的链表是没法使用二分查找的,由于二分查找的高效来自于可以快速经过索引取得任何子数组的中间元素。为了将二分查找的效率和链表的灵活性结合起来,咱们须要更加复杂的数据结构。blog

可以同时拥有二者的就是二叉查找树。排序

 

2.二叉查找树递归


一颗二叉查找树(BST)是一颗二叉树,其中每一个节点都含有一个可比较的键(以及相关联的值)且每一个结点的键都大于其左子树中的任意结点的键而小于右子树的任意结点的键。

 

一颗二叉查找树表明了一组键(及其相应的值)的集合,而同一个集合能够用多颗不一样的二叉查找树表示。

若是咱们将一颗二叉查找树的全部键投影到一条直线上,保证一个结点的左子树中的键出如今它的右边,右子树中的键出如今它的右边,那么咱们必定能够获得一条有序的键列。

 

 

 

  • 查找

 

在二叉查找树中查找一个键的递归算法:

若是树是空的,则查找未命中。若是被查找的键和根结点的键相等,查找命中。不然咱们就在适当的子树中继续查找。若是被查找的键较小就选择左子树,较大就选择右子树。

在二叉查找树中,随着咱们不断向下查找,当前结点所表示的子树的大小也在减少(理想状况下是减半)

 

  • 插入

 

查找代码几乎和二分查找的同样简单,这种简洁性是二叉查找树的重要特性之一。而二叉查找树的另外一个更重要的特性就是插入的实现难度和查找差很少。

当查找一个不存在于树中的结点并结束于一条空连接时,咱们须要作的就是将连接指向一个含有被查找的键的新结点。若是被查找的键小于根结点的键,咱们会继续在左子树中插入该键,不然在右子树中插入该键。

 

  • 分析

 

使用二叉查找树的算法的运行时间取决于树的形状,而树的形状又取决于键被插入的前后顺序。

在最好的状况下,一颗含有N个结点的树是彻底平衡的,每条空连接和根结点的距离都为~lgN。在最坏的状况下,搜索路径上可能有N个结点。但在通常状况下树的形状和最好状况更接近。

 


咱们假设键的插入顺序是随机的。对这个模型的分析而言,二叉查找树和快速排序几乎就是“双胞胎”。树的根结点就是快速排序中的第一个切分元素(左侧的键都比它小,右侧的键都比它大),而这对于全部的子树一样适用,这和快速排序中对于子数组的递归排序彻底对应。

【在由N个随机键构造的二叉查找树中,查找命中平均所需的比较次数为~2lgN。 N越大这个公式越准确】

 

3.平衡查找树


在一颗含有N个结点的树中,咱们但愿树高为~lgN,这样咱们就能保证全部查找都能在~lgN此比较内结束,就和二分查找同样。不幸的是,在动态插入中保证树的完美平衡的代价过高了。咱们放松对完美平衡的要求,使符号表API中全部操做均可以在对数时间内完成。

 

3.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-结点,事情就要麻烦一些。

 

  • 热身:

 

先考虑最简单的例子:只有一个3-结点的树,向其插入一个新键。

这棵树惟一的结点中已经没有可插入的空间了。咱们又不能把新键插在其空结点上(破坏了完美平衡)。为了将新键插入,咱们先临时将新键存入该结点中,使之成为一个4-结点。建立一个4-结点很方便,由于很容易将它转换为一颗由3个2-结点组成的2-3树(如图所示),这棵树既是一颗含有3个结点的二叉查找树,同时也是一颗完美平衡的2-3树,其中全部空连接到根结点的距离都相等。

 

 

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

假设未命中的查找结束于一个3-结点,而它的父结点是一个2-结点。在这种状况下咱们须要在维持树的完美平衡的前提下为新键腾出空间。

咱们先像刚才同样构造一个临时的4-结点并将其分解,但此时咱们不会为中键建立一个新结点,而是将其移动至原来的父结点中。(如图所示)

 

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

 

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

假设未命中的查找结束于一个3-结点,而它的父结点是一个3-结点。

咱们再次和刚才同样构造一个临时的4-结点并分解它,而后将它的中键插入它的父结点中。但父结点也是一个3-结点,所以咱们再用这个中键构造一个新的临时4-结点,而后在这个结点上进行相同的变换,即分解这个父结点并将它的中键插入到它的父结点中去。

咱们就这样一直向上不断分解临时的4-结点并将中键插入更高的父结点,直至遇到一个2-结点并将它替换为一个不须要继续分解的3-结点,或者是到达3-结点的根。

 

  • 总结:

先找插入结点,若结点有空(即2-结点),则直接插入。如结点没空(即3-结点),则插入使其临时容纳这个元素,而后分裂此结点,把中间元素移到其父结点中。对父结点亦如此处理。(中键一直往上移,直到找到空位,在此过程当中没有空位就先搞个临时的,再分裂。)

 

 

★2-3树插入算法的根本在于这些变换都是局部的:除了相关的结点和连接以外没必要修改或者检查树的其余部分。每次变换中,变动的连接数量不会超过一个很小的常数。全部局部变换都不会影响整棵树的有序性和平衡性。

 

{你肯定理解了2-3树的插入过程了吗? 若是你理解了,那么你也就基本理解了红黑树的插入}

 

  • 构造

 

和标准的二叉查找树由上向下生长不一样,2-3树的生长是由下向上的。

 


优势

 

2-3树在最坏状况下仍有较好的性能。每一个操做中处理每一个结点的时间都不会超过一个很小的常数,且这两个操做都只会访问一条路径上的结点,因此任何查找或者插入的成本都确定不会超过对数级别。

完美平衡的2-3树要平展的多。例如,含有10亿个结点的一颗2-3树的高度仅在19到30之间。咱们最多只须要访问30个结点就能在10亿个键中进行任意查找和插入操做。

 

  • 缺点

 

咱们须要维护两种不一样类型的结点,查找和插入操做的实现须要大量的代码,并且它们所产生的额外开销可能会使算法比标准的二叉查找树更慢。

平衡一棵树的初衷是为了消除最坏状况,但咱们但愿这种保障所需的代码可以越少越好。

 

 

4.红黑二叉查找树


【前言:本文所讨论的红黑树之目的在于使读者能更简单清晰地了解红黑树的构造,使读者能在纸上清晰快速地画出红黑树,而不是为了写出红黑树的实现代码。

如果要在代码级理解红黑树,则势必须要记住其复杂的插入和旋转的各类状况,我认为那只有助于增长你们对红黑树的恐惧,实际面试和工做中几乎不会遇到须要本身动手实现红黑树的状况(不少语言的标准库中就有红黑树的实现)。  若对于红黑树的C代码实现有兴趣的,可移步至July的博客。】

 

(理解红黑树一句话就够了:红黑树就是用红连接表示3-结点的2-3树。那么红黑树的插入、构造就可转化为2-3树的问题,即:在脑中用2-3树来操做,获得结果,再把结果中的3-结点转化为红连接便可。而2-3树的插入,前面已有详细图文,实际也很简单:有空则插,没空硬插,再分裂。  这样,咱们就不用记那么复杂且让人头疼的红黑树插入旋转的各类状况了。只要清楚2-3树的插入方式便可。  下面图文详细演示。)

 

  • 红黑树的本质:

红黑树是对2-3查找树的改进,它能用一种统一的方式完成全部变换。

 

  • 替换3-结点

 

★红黑树背后的思想是用标准的二叉查找树(彻底由2-结点构成)和一些额外的信息(替换3-结点)来表示2-3树。

咱们将树中的连接分为两种类型:红连接将两个2-结点链接起来构成一个3-结点,黑连接则是2-3树中的普通连接。确切地说,咱们将3-结点表示为由一条左斜的红色连接相连的两个2-结点。

这种表示法的一个优势是,咱们无需修改就能够直接使用标准二叉查找树的get()方法。对于任意的2-3树,只要对结点进行转换,咱们均可以当即派生出一颗对应的二叉查找树。咱们将用这种方式表示2-3树的二叉查找树称为红黑树。

 

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

⑴红连接均为左连接。

⑵没有任何一个结点同时和两条红连接相连。

⑶该树是完美黑色平衡的,即任意空连接到根结点的路径上的黑连接数量相同。

 

若是咱们将一颗红黑树中的红连接画平,那么全部的空连接到根结点的距离都将是相同的。若是咱们将由红连接相连的结点合并,获得的就是一颗2-3树。

相反,若是将一颗2-3树中的3-结点画做由红色左连接相连的两个2-结点,那么不会存在可以和两条红连接相连的结点,且树必然是完美平衡的。


 

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

(2-3树的深度很小,平衡性好,效率高,可是其有两种不一样的结点,实际代码实现比较复杂。而红黑树用红连接表示2-3树中另类的3-结点,统一了树中的结点类型,使代码实现简单化,又不破坏其高效性。)

 

  • 颜色表示

由于每一个结点都只会有一条指向本身的连接(从它的父结点指向它),咱们将连接的颜色保存在表示结点的Node数据类型的布尔变量color中(若指向它的连接是红色的,那么该变量为true,黑色则为false)。

当咱们提到一个结点颜色时,咱们指的是指向该结点的连接的颜色。

 

  • 旋转

 

在咱们实现的某些操做中可能会出现红色右连接或者两条连续的红连接,但在操做完成前这些状况都会被当心地旋转并修复。

(咱们在这里不讨论旋转的几种状况,把红黑树看作2-3树,天然能够获得正确的旋转后结果)

 

  • 插入

 

在插入时咱们可使用旋转操做帮助咱们保证2-3树和红黑树之间的一一对应关系,由于旋转操做能够保持红黑树的两个重要性质:有序性和完美平衡性。

 

  • 热身:

 

向2-结点中插入新键

(向红黑树中插入操做时,想一想2-3树的插入操做。红黑树与2-3树在本质上是相同的,只是它们对3结点的表示不一样。

向一个只含有一个2-结点的2-3树中插入新键后,2-结点变为3-结点。咱们再把这个3-结点转化为红结点便可)

 

向一颗双键树(即一个3-结点)中插入新键

(向红黑树中插入操做时,想一想2-3树的插入操做。你把红黑树当作2-3树来处理插入,一切都变得简单了)

(向2-3树中的一个3-结点插入新键,这个3结点临时成为4-结点,而后分裂成3个2结点)

 


★一颗红黑树的构造全过程

 

5.平衡二叉树(AVL树)


定义:平衡二叉树(Balance Binary Tree)又称AVL树。它或者是一颗空树,或者是具备下列性质的二叉树:它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1。

若将二叉树上结点的平衡因子BF(BalanceFactor)定义为该结点的左子树深度减去它的右子树深度,则平衡因子的绝对值大于1。

 

其旋转操做 用2-3树的分裂来类比想象。--------------------- 原文:https://blog.csdn.net/yang_yulei/article/details/26066409 

相关文章
相关标签/搜索