博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

第六章动态集和查找

Posted on 2011-10-26 21:38  Amy-lover  阅读(415)  评论(0编辑  收藏  举报

6.1简介

动态集是一个在计算过程中其成员变化的集合。在一些应用中,集合初始为空并且在计算过程中插入元素。往往一个集合可能会增长,它的最大大小不能事先很准确的知道,另一个应用开始于一个大的集合并且在计算过程中删除元素(当集合变成空的时候往往终止)。一些应用既插入又删除元素。开发出来的各种各样的数据结构来代表这些动态的集合。基于需要的操作和访问模式,不同的数据结构式高效的。首先我们描述数组倍增技术,该技术经常需要用来描述动态集合复杂应用的效率。最后,我们抽查了几种常用的发现对动态集合非常有用的数据结构。他们提出适当的抽象数据类型(ADT)的实现。

红-黑树提供了一种平衡树,高效地实现二叉树非常有用。二叉查找树和哈希表都是适当的抽象数据类型的很经常的实现形式。动态等价关系发生在众多的应用,动态等价关系发生在众多应用中,和它们的操作密切相关的联盟查找(Union-Find)抽象数据结构类型ADT,这有一个非常有效的执行,在某些情况下,使用在树中的抽象数据结构类型ADT。

优先级队列许多算法,特别是贪婪算法的重要工具。优先级队列的抽象数据类型ADT的两个高效实现分别是二进制堆和配对的森林,这也被称为“懒配对堆”。

本章介绍这些题目,为了进一步的阅读和更多的广泛的措施,咨询本章末尾的注释和参考文献。

6.2数组倍增

 

在连接动态集时有在一个典型的情况出现,就是当开始计算的时候我们不知道可能需要多大的阵列,分配可能需要的最大的数组往往不能很满意,尽管这是很普通的一种解决方法,一个简单灵活的解决方法是初始时分配一个小的数组,当它太小变的很明显的时候倍增它的大小。为了能使该方法实施,我们需要跟踪当前数组有多满和目前需要分配给多少个输入。java利用length域来自动跟踪后来的信息,但是第一个数字是程序员的责任,它依赖于使用数组的应用程序。

假设我们有一个组织者类setArray有两个域,setSize和elements后者是一个数组元素的类型,我们假设是Object,初始,我们可能在这个类里面构建一个对象,如下:

setArray mySet=new setArray();

mySet.setSize=0;

mySet.elements=new Object[100];

现在每次一个元素被追加到mySet,程序也增长setSize。在插入一个新元素之前,然而,程序应该肯定有空间,并且如果没有,倍增数组大小。通过分配一个是现在大小2倍的新的数组来完成的,然后将所有元素转移到新的数组中。实现代码如下:

image

代价高的部分是转移元素。然而,我们现在将说明插入n个有序的元素到一个集合中的总的开销是O(n)。

假设插入第n+1个元素的时候出发了倍增数组操作。设t是一个元素从老得数组中转移到新的数组中的代价(假设t是某个常数)。那么n的转移作为这个数组加倍操作的一部分,但随后发生n / 2的转移发生在先前的阵列倍增操作,之前的n/4也是如此,等等。集合生成以后所有的转移的总成本都超过2tn.(image  a1=n,q=1/2,Sn=2n-n/2^n-1<2n)。

这是一个很简单的例子,在该例子中它可能摊销也可能展开,临时昂贵的操作的开销,以至于每个操作的平均负载是有界的一个常数。摊销时间分析将在下一节讲解。

6.3 摊销时间分析

就像我们在前一节中看到的那样,在相同类型的单个操作中,所做的工作差别很大这种情况可能会出现,但长序列的操作的总时间比单个操作乘于操作序列的长度的最坏情况的时间少得多,这种情况在连接动态集和他们的相关操作是相当频繁的出现的。一个称为摊销时间分析的技术涉及到在这些情况下提供更准确的分析,摊销这个名词来自于企业会计实务的分散大成本,事实上是在一个单一的时间段内带来的成本,多个时间段涉及到带来成本的原因。在算法分析的情况下,一个动作的大的开销,分散到许多操作中,而这些许多操作开销都是比较小的。本节对摊销时间分析给出了一个简单的介绍。该技术在概念上是简单的,尽管它需要创造性为难题想出高效的方案。

假设我们有一个抽象数据类型(ADT)而且我们想要利用摊销时间分析法来分析它的操作。我们使用术语“单一操作”来代表一个操作的一次执行。摊销时间分析法基于以下方程式,它适用于出现在一些计算过程中出现的这个抽象数据结构的每一个单一操作:

摊销成本=实际成本+会计成本

这创造性的是去设计一个为能到达两个目标的单一操作的会计成本的系统:

1,在任何合法的操作序列中,从分析抽象数据类型对象的建立开始,会计成本的总额是非负的。

2,尽管实际成本从一个单一操作到另一个单一操作的可能波动很大,但去分析每个操作的摊销成本是可行的(例如,它是比较经常的)。

如果这两个目的达到了,那么一个操作序列的总的摊销成本是实际总成本的上限,总摊销成本是经得起分析

直观地,会计成本的总额就像储蓄账户。当好的时机, 我们进行存款保存,以备不时之需. 当不好的时侯到来时,以一个不同寻常的昂贵的单一操作的形式.我们取款.然而,为了保持支付能力,我们的账户平衡不能为负.

设计一个会计成本的系统的核心思想是”正常”的单一操作的会计成本应该是正数, 然而异常昂贵的单一操作,接收到一个负的会计成本. 也就是说,负的会计成本应该抵消异常昂贵的高的实际成本, 所以,摊销成本大约在同一“正常”和“异常的昂贵的”单一操作.摊销成本可能依赖于数据结构中有多少元素,但是它应该相对独立于数据结构的细节. 计算出多大才可以产生正的”费用”往往需要创造性,并可能涉及到为了达到一个相当小的总量的试验和错误的程度,但是足够大去组织“账户余额”成为负数.

例6.1    会计栈数组倍增计划

考虑栈ADT,有两个操作,push和pop,用数组实现。(在本例子中,我们将忽略访问操作的代价,因为它们不会改变栈,而且时间复杂度为O(1)),想6.2节描述的一样,数组倍增应用的幕后是按所需扩展数组。当没有重新设置数组大小的时候,push和pop的实际操作成本是1,并且push的实际操作成本是1+nt,对于某个常数t,如果它涉及到倍增数组大小从n到2n并且复制n个元素到新的数组中去,。

push的最坏情况的实际时间是O(n).看最坏情况的实际时间可能使它的实现看起来好像很低效,然而,摊销时间分析给出了一个更精确的图片,我们可以建立下列会计计划:

1,push的会计成本并不需要数组倍增是2t。

2,从n到2n,push的会计成本需要倍增数组是-nt+2t。

3,pop的会计成本是0.

在会计成本中系数2在建栈的时候被选出来已经足够大了,会计成本永远不会为负。我们来随便看一下这个,假设倍增发生在数组大小为N,2N,4N,8N等等,我们考虑最坏情况,只有入栈发生。“会计平衡”------会计成本的净总和------将增长到2tN,那么第一个负的冲击将使它减少到Nt+2t,然后在第二次倍增钱,它将回增到3Nt,在它增长到3Nt时,它将下跌回到Nt+2t。从这开始它将增长到5Nt,得以削减到Nt+2t,增长到9Nt时,将被削减到Nt+2t,等等。因此,对于ADT栈来说是一个有效的会计方案。

有了摊销方案,每个push操作的摊销成本就是1+2t,不管它是否引起数组倍增,并且每个pop操作的摊销成本是1.因此我们可以说push和pop在最坏情况下的摊销成本是O(1)。

更加复杂的数据结构则需要更加复杂的摊销方案,需要更有创造性的发明。在本章后面的章节中,我们将遇到ADT及其实现,需要摊销方案来描述他们的效率。

6.4  红-黑树

红黑树是满足特定的结构要求的二叉树。这种结构要求暗示了n个节点的红黑树的高度不能超过2lg(n+1).也就是它的高度是在最平衡二叉树的两个高度中的一个方面。红黑树最广泛的使用是二叉查找树,但这并不仅仅是应用。本节将说明怎样去利用红黑树来高效的保持二叉树的平衡。其他一些保持二叉树平衡的方案将在本章末尾注释和参考文献里提到。我们选择关注红黑树是因为删除过程是比大多数替代简单。

这里介绍完一些符号以后,我们将重温二叉树,然后我们介绍红黑树需要的结构化属性,并说明怎样在删除和插入操作中维护它。

红黑树是类RBtree的一个对象,RBtree的实现和2.3.3节的BinTree数据结构的实现有很多相似的地方;然而,具体和接口很不相同。这是因为红黑树比BinTree数据结构中一般的二叉树多了一个特定的目的,有修改它的数据结构的操作,然而BinTree数据结构并没有这些操作的定义。在BinTree数据结构中零代表一棵空树,红黑树的操作有rbtInsert和rbtDelete,和rbtSearch,它们分别代表插入,删除和查找一个给定的值,红黑树没有提供到子树的直接访问,因为它是BinTree数据结构,然而,这样的访问功能,可加用子树是二叉搜索树的理解,但是在红黑树中是不必要的。

RBtree类适合于用来实现ADT Dictionary,或者其他需要平衡二叉树的ADT。红黑树的节点在一些Elemnet类中是对象;红黑树算法的细节并不重要。在数据词典中这将是有序的元素类型。许多排序的关于元素,键和键之间的比较的常规都将在这里介绍。我们假设在Element类中一个域命名为key,并且在Key类中。由于符号的常规,我们假设键值乐意用常规的操作如“<”来比较。

 

正确地画树

正确画树的思想有助于想象许多关于二叉树和红黑树的概念,本书使用正确地绘制所有插图的树。

定义6.1

正确在二维平面上绘制一棵树,如果:

1.每个节点都有一个点和每个边都有一个线性的部分或者一个弯曲的链接一个父节点到一个子节点,(在一幅图中,一个节点是一个圆圈或者一个类似的形状,它的点可以考虑位于中心,边考虑指向这些点)

2.任一节点的左右孩子都分别位于左右两边一,那些节点在水平位置上。

3.对于任意边uv,u是父节点,在边uv上,没有点有相同的水平位置(例如,直属或者之上)作为u的直系祖先。

在一个正确画树中,在一个给定树的左子树的所有节点,都在根的左边,在一个给定树的右子树的所有节点都在根的右边,只考虑他们的水平位置。如果一个二叉树被正确的画出来,那么席卷一条垂直线由左到右,遇到在他们的中序遍历顺序中的节点。

空树作为外部节点

对二叉查找树,尤其是红黑树,把空树作为一个特殊类型的节点很便利,称为“外部节点”。外部节点在与2-树联系介绍(3.4.2节)和用来进行分析决策树(4.7节)。在本方案,一个外部节点不能有任何孩子,并且一个内部节点必须有两个孩子,只有内部节点包含数据,包括键值。在BinTree ADT方面,我们将认为一个空树作为一个到外部节点的边,所有其他子树扎根在内部节点。

image

a图一个具有4个节点的节点组,圆圈里,它的5个主要子树,用灰色表示:小节点代表外部节点,b图新树T:主要子树被外部节点代替。

定义6.2 节点组和它们的主要子树

一个节点组是任何一个二叉树的内部节点的连接组,如果S的根的父节点在这个组中,但S没有任何节点,那么子树S是一个节点组的主要的子树,一个节点组的主要子树可以是一个外部节点(空树)。

如上图说明了一个节点组合它的主要子树。一个节点组可以认为是一个新树T的内部节点,想图b中建议的那样,节点组被提取出来,外部节点被贴在主要子树节点所在的位置。一个节点组主要子树的数目总是比该组中节点的数目多1个。

6.4.1 二叉查找树

在二叉查找树中再节点中的键值满足一下约束条件:

定义6.3 二叉查找树的属性

二叉树的节点的键是从一个有序集来的,则如果每个节点的键值都大于它的左子树的键,且都小于等于它的右子树的键,该二叉树拥有二叉搜索树属性,在这种情况下二叉树称为二叉查找树。

二叉查找树的中序遍历长生一个有序键值的序列。正确绘制的二叉树是否是二叉搜索树是很容易通过检查确定,通过一个从左到右垂直的线,就像定义6.1提到的一样,看例子图6.2,图中所绘,二叉查找树可以极大地在他们的平衡度。

image

图6.2

两个二叉查找树在相同集合上的键值,有不同度的平衡:黑色节点是空树,在本节中也称为外部节点

查找一个特定的键值,我们从根节点开始,然后顺着左子树或者右子树决定于要搜索的键值是小于还是大于当前节点的键值。这一过程为所有二叉查找树设置了模式,从红黑树中插入和删除操作在6.4.5和6.4.6节包含了隐藏在他们中的相同的查找逻辑。

算法6.1 二叉查找树的检索

输入:bst,二叉查找树;K,要搜索的键值

输出:树中的一个对象,它的键值域是K,或者如果K不是树中任何节点的键值,为null。

 image

当查找一个键值的时候,我们用检查的一棵树的内部节点的数目作为我们工作的计量(尽管,在高水平语言算法中,K在一棵树中与键值比两次的,它是合理的计数作为一个三路的比较,像我们在1,。6节中争论的那样,无论哪种方式,比较的次数和检查的节点数目是成正比的)。在最坏情况下(包括K不在该树中)检查的节点的数目是树的高度。(在本节中,只有一个内部节点的树的高度是1,因为空树是作为外部节点来处理的;在2.3.3节,这样一棵树的高度定义为0,这种不同并不重要,只要一种惯例一直持续的用下去就好了)

假设一棵有n个节点的树。如果树的结构是任意的(因此可能组成很长的链),最坏情况下是O(n)。如果树尽可能平衡,最坏情况下检查节点的大致是lgn。在二叉查找树上的所有操作都遵循bstSearch模式和有最坏的情况正比于树的高度。一个平衡树的目的是将最坏情况嫌少到O(logn).

6.4.2 二叉旋转树

二叉树的结构可以通过被称为“旋转”的操作进行局部修改,这并不影响二叉查找树的属性。尽管对于红黑树,重新调整平衡的操作可以描述为不用旋转,旋转在其本身是很有用的操作,并且他们为更复杂的重构操作,提供了一个好的说明。事实上,更复杂的重构操作可以有一系列的旋转建立起来。

一次旋转涉及到有两个相连节点的一个组,对于父节点和子节点设置为p和c,该组的三个主要的子树。图6.3演示了下列描述,15是p,25是c。p和c之间的边改变方向,中间的主要子树改变父节点,从c到p。因为c是现在的组的根,p的当前父节点是现在c的父节点。所以它现在必须有一个到c代替p的边,所以在旋转期间三条边都调整了。

image

 

 

 图6.3 左旋转将在左边的树转换成在右边的树(节点50的右子树没有画出来)。右旋转将在右边的树转换到在左边的树。

在一左旋转p在c的左边,所以p下沉,c上升,并且到中子树的边移动到左边和p连接起来。左主要子树随p一起下沉;右主要子树随c一起上升;中主要子树继续留在同一层。右旋转与左旋转相反;也就是,做了左旋转紧接着右旋转上同组的两个节点离开树不变。如图6.3,正确选择旋转可提高二叉树的平衡度。

6.4.3 红黑树的定义

红黑树是类RBtree的一个对象。我们定义这个类有4个实例字段,root,leftSubtree,rightSubtree和color。color域指明了树的根节点的颜色,尽管单个节点没有一个color域,每个节点都是某个子树的根,因此每个节点都有一个颜色与它相关联。为了能实施,我们为一棵树而不是该节点定义color作为一个域,所以节点类型不用必须具体到红黑树。然而,当我们抽象地讨论树和节点的时候,我们说的节点是有颜色的。

节点颜色可能是红的可能是黑的(常量定义在类里)。一个节点在删除的过程中可能暂时是灰色的但是结构不是红黑树,直到条件改变。空树(由常数零代表,也称为外部节点)定义为黑色的。

定义6.4 红黑树

设T是一个二叉树,每个节点都有颜色,红色或者黑色,并且所有外部节点都是黑色的。一个指向黑色节点的边是黑色边,一条路径的黑色长度是指在这条路径是黑色边的条数。节点的黑色深度是从根节点到该节点的路径的黑色长度。一个从一特定节点到一外部节点的路径称为到该特定节点的外部路径。如果有且只有满足下面条件,树T就是红黑树:

1,一个红节点没有红孩子。

2,从一个给定节点u的所有外部路径的黑色长度是相同的;这个值称为u的黑色长度。

3,根是黑色的。

如果一个树T上面的条件都满足,除了根式红色的,就称它为几乎红黑树(ARB树)。

图6.4是图6.2中的相同的键值下,几种可能的红黑树。浅色的节点是红色的,每棵树的根都有两个黑色高度最右边的树对一个有6个节点的红黑树而言,有可能得到的最大高度。它的高度比图6.2的右树的高度小一些。

我们可以通过画图,得到更广阔的关于红黑树结构的视野,红节点和它的父母节点在一个水平上。根据这个惯例,集合深度和黑色深度一致,并且所有的外部节点都出现在同一深度!

image

图6.4相同键值的几种红黑树:粗边为黑色边

image

图6.5 根据黑色深度的惯例画出的红黑树,为了清晰,我们画了一个箭头指向根。

图6.4中的树在图6.5中利用该惯例重新画了。称为黑色深度公约。

现在让我们看一些ARB树,在图6.5中,根为60的子树在最下面的图中就是ARB树的例子,如果这可子树是整个树,我们将简单的将根的颜色改为黑色,那么我们将有一颗RB树。事实上,看看这一图的其他有红根的子树,我们可以看到所有的都是ARB树。下面归纳性的定义相当于定义6.4,在两个定义,定义相同的结构,但是新的定义给出了更多的细节。注意 一个RBh树是一个黑色高度为h的红黑树。

定义6.5 RBh树和ARBh树

二叉树的节点是红色或者黑色的,外部节点为黑色,为RBh树或者ARBh树,如下:

1,一个外部节点是一棵RB0树。

2,当n≥1时,如果一棵二叉树它的根节点是红色的并且它的左右子树都是一棵RBh-1的树,那么这棵二叉树是一个ARBh树。

3,当n≥1时,如果一棵二叉树它的根式黑色的,并且它的左右子树都是一棵RBh-1的树,那么这棵二叉树是一棵RBh树。

做练习6.4(画RBh树和ARBh树)将帮助更清楚的理解该定义。

引理6.1任何一棵RBAh的树或者RBh的树的黑色高度被定义,并且是h。

证明见练习6.5.

6.4.4红黑树的规模和深度

仅仅从定义,没有看任何算法,我们可以导出关于红黑树的一些有用的方面。这些事实很容易通过归纳证明,用定义6.5。

引理6.2 设T为一棵RBh树,也就是,设T为一棵黑色高度为h的红黑树。

那么:

1,T 至少有image 个黑色内部节点

2,T最多有image内部节点

3,任何一个黑色节点的深度最多是它的黑色深度的两倍。

设A是一棵ARBh树,也就是,设A是一个黑色深度为h的几乎红黑树。那么:

1,A至少有image 内部黑色节点

2,A最多有image 个内部节点

3,任何黑色节点的深度最多是它的黑色深度的两倍

该引理导出了n个节点中的任何一个节点的深度范围,内部节点的数目。下面的定理说明了在节点数目相同的红黑树中,最长的路径最多是最平衡二叉树的最长路径的两倍。

定理6.3设T是一棵有n个节点的红黑树。那么没有节点的深度大于image 。换句话说,树T在某种意义上来说高度最大为image

证明:设h为树T的黑色高度,内部节点的数目n至少都是内部黑色节点,,内部黑色节点至少有image ,根据引理6.2 所以image 。最大深度的节点是某个外部节点,所有外部节点的黑色深度为h,通过引理6.2,因此任何一个外部节点的深度最多为2h。

6.4.5 红黑树的插入

红黑树的定义在颜色上指明了一个约束条件,好友一个约束条件在黑色高度上。插入红黑树的思想是插入一个红节点,从而保证黑色高度的约束条件依然完好。然而,新的红色节点可能违反要求红色节点不能有红色孩子。当维护红黑树的约束条件,我们可以通过某个改变颜色的结合和结构,修复这次冲突。

插入一个元素K产生的第一个阶段本质上是和在一棵BST中查找一个元素K是一样的,还有到达一个外部节点,因为查找该键值失败。下一步就是用包含一个节点K的一棵树代替一棵空树。最后一步当从递归调用回来再执行,是去修复任何颜色的冲突,在任何时刻黑色高度的约束条件都不冲突。

例6.2 插入红黑树阶段一

在看整个算法之前,让我们考虑插入的第一个阶段发生了什么,如果我们插入一个新键值70到红黑树中,如图6.5所示,在所有的三棵树中70和根比较,比根大,所以查找下降到右子树,那么70和60比较,并再次下降到右子树,在这里70和80比较。现在查找走向了左边并且遇到了外部节点,包含80的节点的左子树。外部节点被新的包含键值70的新的红色节点代替并有了两个外部节点做儿子。上层树现在的构造如图6.6。在低树和中树中,新节点的位置是相似的,但是它的双亲节点是黑色的,所以没有颜色的冲突,并且该过程已经结束。图6.6的上层树中,一个颜色的冲突发生了,因为红色节点80有一个红色儿子70.这个冲突必须被修正才可以完成这次插入操作。描述完修正方法后,我们将回到本例。

image

图6.6 一个红黑树在插入键值70到图6.5的上层树之后颜色约束的冲突

定义6.6 集群和临界集群

我们定义一个集群作为内部节点的集合,内部节点是由黑色节点和所有可以从黑色节点经过非黑色边到达的红色节点组成。(因此,每个集群都准确的有一个黑色节点,称为集群的根)

如果集群中任何一个节点都是通过一条路径可以到达的,而且该路径比从集群的根开始的路径都要长,那么这个集群就称为“临界集群”(因为集群中的所有路径都是由非黑色的边组成,路径长度意味着一些红色节点到另一个红色节点有一条边)。

通过定义6.2,一个集群的主要子树是那些根不在集群里的子树,但是这些子树的父节点在集群中,通过集群的定义,一个集群的主要子树的根是黑色的。一个主要子树一定是外部节点(空树)。

例子6.3 红黑树中的集群

在图6.6,节点40是一个集群,节点(20,30)是一个集群,节点(60,50,80,70)是一个集群。后者的集群是一个临界集群,因为从该集群的根60经过两个路径长度可以到达70。集群40的主要子树植根与20和60.集群(60,50,80,70)的主要子树是5个外部节点,集群(20,30)的主要子树是三个外部节点。

如果如果是黑色高度定义为集群的根,并且有键值h,那么它定义的很好并对于集群中其他节点而言,等于h,因为他们都是红色节点。这个黑色高度很好的定义并等于h,仅且只有每个主要子树有黑色高度h-1.我们将看到在整个插入过程中这个条件都维持着。

利用集群和临界集群的概念,我们可以笼统描述的红黑树在插入一个新节点的过程中可能发生的冲突的定义。如果在树中没有临界集群,就没有冲突,则操作就完成了。一个临界集群可以有三到四个节点。图6.6说明了有四个节点的例子,如果节点50是空的(被一个外部节点代替),那么该集群将是临界集群,并且它有三个节点。

在一个插入操作之前,一个红黑树没有临界集群。就像我们看到的那样,插入的第一个阶段可能产生一个临界集群。在重新调整平衡(阶段二)策略是修复一个临界集群要么离开没有临界集群,要么在树中产生一个更高的新集群。在任何时候都多余一个临界集群,那么修复就成功了。所以重新调整平衡最终成功了。修复的方法依赖于临界集群里有三个还是四个节点。

首先,考虑一个四个节点的临界集群,如图6.6(60,50,80,70)的这一集群。我们对集群的根进行颜色翻转,我们称该根为r(r初始为黑色的),并且它有两个儿子(初始全部为黑色),也就是,我们使根r为红,两个儿子为黑。这使r的黑色高度增加了1,如图6.7,r就是节点60.然而,从r的父节点到r的边不再是黑色边了,所以从父节点穿过r的路径的黑色长度没有改变,并且父节点的黑色高度仍然定义良好。颜色翻转解决了颜色冲突:路径是黑,红,红,现在是红,黑,红

image

图6。7颜色翻转修复图6.6中的四个节点的临界集群

如果r碰巧是整棵树的根,通过颜色翻转它变成了红色,它将在插入过程结束时变回黑色(当第一个节点插入到空树时,整棵树的根也可以为红色)整棵树的黑色高度改变的唯一时刻就是在插入过程中当根变成红色时.

因为颜色翻转改变了r,先前集群的根,变成红色并将它放入不同的集群,这就有一种可能性,r的父节点也是红节点并且新的集群称为了临界集群.在这种情况下这个新的集群一定要修复.

例6.4 红黑树的插入和颜色翻转

设键值85,然后是90,插入到图6.7中.第一次插入不会产生颜色翻转.第二次插入的阶段一产生如图6.8上面的情况.有(80.70.85.90)组成的临界集群.这种情况出现在执行完颜色翻转之后图6.8下面的那个图所示。节点80已经加入到了(40.60)的集群使得这个集群称为了三个节点的临界集群。颜色翻转对临界集群的三个节点是无效的(看练习6.7)。所以需要一个新的技术来修复这个临界集群。

现在我们转向修复三个节点的临界集群的技术。称节点为L,M,R,从左到右的顺序(记住,这棵树假设是正确画的)。这个集群有四个主要子树。依照从左到右的顺序,称为LL,LR,RL,RR。回溯每个主要子树的根都一定是黑色的,或者它们是集群的一部分。临界集群的根或者是L或者是R,因为否则它不能包含一个长度为2的路径。四个可能的配置显示为图6.9的树(a)到(d)。被视为一棵三个节点的树,集群是不平衡的,解决很简单就是集群自己重新调整,维持它的黑色高度。也就是,M称为该集群的新根,并变成黑色;L变成新的左孩子,R变成新的右孩子,并且它们都变成红色。现在主要子树重新附上,保持它们的顺序(因此保持二叉查找树的属性)。LL和LR分别成为L的左右孩子;RL和RR分别成为R的左右孩子。注意在重新调整平衡前,集群的四个不同的安排是可能的,但是重新调整平衡之后,它们都是相同的。

image

图6.8,在上面那棵树,颜色翻转修复四个节点的临界集群给定较低的这棵树,但是这棵树有一个新的临界集群(40.60.80).

image

图6.9 重新调整平衡修复三个节点的任意临界集群.四种可能的初始配置,(a)到(d),成为右边同样的最终的配置。

image

图6.10重新调整平衡,在图6.8的底下的树临界集群(40.60.80)的结果:节点60现在是树的根。

例6.5红黑树的插入和重新调整平衡

在图6.8中临界集群(40.60.80)