收藏 分享(赏)

左偏树的特点及其应用.doc

上传人:lxhqcj 文档编号:6624896 上传时间:2019-04-18 格式:DOC 页数:23 大小:239.98KB
下载 相关 举报
左偏树的特点及其应用.doc_第1页
第1页 / 共23页
左偏树的特点及其应用.doc_第2页
第2页 / 共23页
左偏树的特点及其应用.doc_第3页
第3页 / 共23页
左偏树的特点及其应用.doc_第4页
第4页 / 共23页
左偏树的特点及其应用.doc_第5页
第5页 / 共23页
点击查看更多>>
资源描述

1、第 1 页 共 23 页左左 偏偏 树树 的的 特特 点点 及及 其其 应应 用用【摘要】本文较详细地介绍了左偏树的特点以及它的各种操作。第一部分提出可并堆的概念,指出二叉堆的不足,并引出左偏树。第二部分主要介绍了左偏树的定义和性质。第三部分详细地介绍了左偏树的各种操作,并给出时间复杂度分析。第四部分通过一道例题,说明左偏树在当今信息学竞赛中的应用。第五部分对各种可并堆作了一番比较。最后总结出左偏树的特点以及应用前景。【关键字】 左偏树 可并堆 优先队列【目录】一、引言 2二、左偏树的定义和性质 22.1 优先队列,可并堆 .22.1.1 优先队列的定义 22.1.2 可并堆的定义 22.2

2、左偏树的定义 .32.3 左偏树的性质 .4三、左偏树的操作 53.1 左偏树的合并 .53.2 插入新节点 .73.3 删除最小节点 .83.4 左偏树的构建 .83.5 删除任意已知节点 .93.6 小结 .12四、左偏树的应用 134.1 例数字序列(Baltic 2004) 13五、左偏树与各种可并堆的比较 155.1 左偏树的变种斜堆 .155.2 左偏树与二叉堆的比较 .165.3 左偏树与其他可并堆的比较 .16六、总结 18第 2 页 共 23 页【正文】一、引言优先队列在信息学竞赛中十分常见,在统计问题、最值问题、模拟问题和贪心问题等等类型的题目中,优先队列都有着广泛的应用。

3、二叉堆是一种常用的优先队列,它编程简单,效率高,但如果问题需要对两个优先队列进行合并,二叉堆的效率就无法令人满意了。本文介绍的左偏树,可以很好地解决这类问题。二、左偏树的定义和性质在介绍左偏树之前,我们先来明确一下优先队列和可并堆的概念。2.1 优先队列,可并堆2.1.1 优先队列的定义优先队列(Priority Queue)是一种抽象数据类型(ADT),它是一种容器,里面有一些元素,这些元素也称为队列中的节点(node)。优先队列的节点至少要包含一种性质:有序性,也就是说任意两个节点可以比较大小。为了具体起见我们假设这些节点中都包含一个键值(key),节点的大小通过比较它们的键值而定。优先队

4、列有三个基本的操作:插入节点(Insert),取得最小节点(Minimum) 和删除最小节点(Delete-Min)。2.1.2 可并堆的定义可并堆(Mergeable Heap)也是一种抽象数据类型,它除了支持优先队列的三个基本操作(Insert, Minimum, Delete-Min),还支持一个额外的操作合并操作:H Merge(H 1,H2)Merge( ) 构造并返回一个包含 H1 和 H2 所有元素的新堆 H。前面已经说过,如果我们不需要合并操作,则二叉堆是理想的选择。可惜合并二叉堆的时间复杂度为 O(n),用它来实现可并堆,则合并操作必然成为算法的瓶颈。左偏树(Leftist

5、Tree)、二项堆(Binomial Heap) 和 Fibonacci 堆(Fibonacci Heap) 都是十分优秀的可并堆。本文讨论的是左偏树,在后面我们将看到各种可并堆的比较。第 3 页 共 23 页2.2 左偏树的定义左偏树(Leftist Tree)是一种可并堆的实现。左偏树是一棵二叉树,它的节点除了和二叉树的节点一样具有左右子树指针( left, right )外,还有两个属性:键值和距离(dist)。键值上面已经说过,是用于比较节点的大小。距离则是如下定义的:节点 i 称为外节点(external node),当且仅当节点 i 的左子树或右子树为空 ( left(i) = N

6、ULL 或 right(i) = NULL );节点 i 的距离 (dist(i)是节点 i 到它的后代中,最近的外节点所经过的边数。特别的,如果节点 i 本身是外节点,则它的距离为 0;而空节点的距离规定为-1 (dist(NULL) = -1)。在本文中,有时也提到一棵左偏树的距离,这指的是该树根节点的距离。左偏树满足下面两条基本性质:性质 1 节点的键值小于或等于它的左右子节点的键值。即 key(i)key(parent(i) 这条性质又叫堆性质。符合该性质的树是堆有序的(Heap-Ordered)。有了性质 1,我们可以知道左偏树的根节点是整棵树的最小节点,于是我们可以在 O(1) 的

7、时间内完成取最小节点操作。性质 2 节点的左子节点的距离不小于右子节点的距离。即 dist(left(i)dist(right(i) 这条性质称为左偏性质。性质 2 是为了使我们可以以更小的代价在优先队列的其它两个基本操作(插入节点、删除最小节点)进行后维持堆性质。在后面我们就会看到它的作用。这两条性质是对每一个节点而言的,因此可以简单地从中得出,左偏树的左右子树都是左偏树。由这两条性质,我们可以得出左偏树的定义:左偏树是具有左偏性质的堆有序二叉树。下图是一棵左偏树:0 1 2 18 3 1 12 1 18 0 24 0 18 0 37 0 33 0 6 1 14 0 21 0 17 0 6

8、2 23 0 26 0 10 1 key dist Node:keydistL R第 4 页 共 23 页2.3 左偏树的性质在前面一节中,本文已经介绍了左偏树的两个基本性质,下面本文将介绍左偏树的另外两个性质。我们知道,一个节点必须经由它的子节点才能到达外节点。由于性质 2,一个节点的距离实际上就是这个节点一直沿它的右边到达一个外节点所经过的边数,也就是说,我们有性质 3 节点的距离等于它的右子节点的距离加 1。即 dist( i ) = dist( right( i ) ) + 1 外节点的距离为 0,由于性质 2,它的右子节点必为空节点。为了满足性质 3,故前面规定空节点的距离为-1。我

9、们的印象中,平衡树是具有非常小的深度的,这也意味着到达任何一个节点所经过的边数很少。左偏树并不是为了快速访问所有的节点而设计的,它的目的是快速访问最小节点以及在对树修改后快速的恢复堆性质。从图中我们可以看到它并不平衡,由于性质 2 的缘故,它的结构偏向左侧,不过距离的概念和树的深度并不同,左偏树并不意味着左子树的节点数或是深度一定大于右子树。下面我们来讨论左偏树的距离和节点数的关系。引理 1 若左偏树的距离为一定值,则节点数最少的左偏树是完全二叉树。证明:由性质 2 可知,当且仅当对于一棵左偏树中的每个节点 i,都有 dist(left(i) = dist(right(i) 时,该左偏树的节点

10、数最少。显然具有这样性质的二叉树是完全二叉树。定理 1 若一棵左偏树的距离为 k,则这棵左偏树至少有 2k+1-1 个节点。证明:由引理 1 可知,当这样的左偏树节点数最少的时候,是一棵完全二叉树。距离为 k 的完全二叉树高度也为 k,节点数为 2k+1-1,所以距离为 k 的左偏树至少有 2k1 -1 个节点。作为定理 1 的推论,我们有:性质 4 一棵 N 个节点的左偏树距离最多为 log(N+1) -1。证明:设一棵 N 个节点的左偏树距离为 k,由定理 1 可知,N 2k+1-1,因此 k log(N+1) -1。有了上面的 4 个性质,我们可以开始讨论左偏树的操作了。第 5 页 共

11、23 页三、左偏树的操作本章将讨论左偏树的各种操作,包括插入新节点、删除最小节点、合并左偏树、构建左偏树和删除任意节点。由于各种操作都离不开合并操作,因此我们先讨论合并操作。3.1 左偏树的合并C Merge(A,B)Merge( ) 把 A,B 两棵左偏树合并,返回一棵新的左偏树 C,包含 A 和 B 中的所有元素。在本文中,一棵左偏树用它的根节点的指针表示。在合并操作中,最简单的情况是其中一棵树为空(也就是,该树根节点指针为 NULL) 。这时我们只须要返回另一棵树。若 A 和 B 都非空,我们假设 A 的根节点小于等于 B 的根节点(否则交换A,B) ,把 A 的根节点作为新树 C 的根

12、节点,剩下的事就是合并 A 的右子树right(A) 和 B 了。right(A) Merge(right(A), B)合并了 right(A) 和 B 之后,right(A) 的距离可能会变大,当 right(A) 的距离大于 left(A) 的距离时,左偏树的性质 2 会被破坏。在这种情况下,我们只须要交换 left(A) 和 right(A)。若 dist(left(A) dist(right(A),交换 left(A) 和 right(A)第 6 页 共 23 页最后,由于 right(A) 的距离可能发生改变,我们必须更新 A 的距离:dist(A) dist(right(A) +

13、1不难验证,经这样合并后的树 C 符合性质 1 和性质 2,因此是一棵左偏树。至此左偏树的合并就完成了。下图是一个合并过程的示例:合并流程第 7 页 共 23 页我们可以用下面的代码描述左偏树的合并过程:Function Merge(A, B)If A = NULL Then return BIf B = NULL Then return AIf key(B) dist(left(A) Thenswap(left(A), right(A)If right(A) = NULL Then dist(A) 0Else dist(A) dist(right(A) + 1return AEnd Func

14、tion下面我们来分析合并操作的时间复杂度。从上面的过程可以看出,每一次递归合并的开始,都需要分解其中一棵树,总是把分解出的右子树参加下一步的合并。根据性质 3,一棵树的距离决定于其右子树的距离,而右子树的距离在每次分解中递减,因此每棵树 A 或 B 被分解的次数分别不会超过它们各自的距离。根据性质 4,分解的次数不会超过 log(N1+1) + log(N2+1) -2,其中 N1和 N2 分别为左偏树 A 和 B 的节点个数。因此合并操作最坏情况下的时间复杂度为 O( log(N1+1) + log(N2+1) -2) = O(log N1 + log N2)。3.2 插入新节点单节点的树

15、一定是左偏树,因此向左偏树插入一个节点可以看作是对两棵左偏树的合并。下面是插入新节点的代码:Procedure Insert(x, A)B MakeIntoTree(x)A Merge(A, B)End Procedure由于合并的其中一棵树只有一个节点,因此插入新节点操作的时间复杂度是 O(logn)。第 8 页 共 23 页3.3 删除最小节点由性质 1,我们知道,左偏树的根节点是最小节点。在删除根节点后,剩下的两棵子树都是左偏树,需要把他们合并。删除最小节点操作的代码也非常简单:Function DeleteMin(A)t key(root(A)A Merge(left(A), righ

16、t(A)return tEnd Function由于删除最小节点后只需进行一次合并,因此删除最小节点的时间复杂度也为 O(logn)。3.4 左偏树的构建将 n 个节点构建成一棵左偏树,这也是一个常用的操作。算法一 暴力算法逐个节点插入,时间复杂度为 O(nlogn)。算法二 仿照二叉堆的构建算法,我们可以得到下面这种算法: 将 n 个节点(每个节点作为一棵左偏树)放入先进先出队列。 不断地从队首取出两棵左偏树,将它们合并之后加入队尾。 当队列中只剩下一棵左偏树时,算法结束。5 64321第 9 页 共 23 页5 643 215 6 4321 564321214356214356构建流程下面

17、分析算法二的时间复杂度。假设 n=2k,则:前 次和并的是两棵只有 1 个节点的左偏树。2n接下来的 次合并的是两棵有 2 个节点的左偏树。4接下来的 次合并的是两棵有 4 个节点的左偏树。8接下来的 次合并的是两棵有 2i-1 个节点的左偏树。in2合并两棵 2i 个节点的左偏树时间复杂度为 O(i),因此算法二的总时间复杂度为:。 ki knOnnOnOn 1 )(2(*)2*()3(*8)(4)1(*23.5 删除任意已知节点接下来是关于删除任意已知节点的操作。之所以强调“已知” ,是因为这里第 10 页 共 23 页所说的任意节点并不是根据它的键值找出来的,左偏树本身除了可以迅速找到最

18、小节点外,不能有效的搜索指定键值的节点。故此,我们不能要求:请删除所有键值为 100 的节点。前面说过,优先队列是一种容器。对于通常的容器来说,一旦节点被放进去以后,容器就完全拥有了这个节点,每个容器中的节点具有唯一的对象掌握它的拥有权(ownership ) 。对于这种容器的应用,优先队列只能删除最小节点,因为你根本无从知道它的其它节点是什么。但是优先队列除了作为一种容器外还有另一个作用,就是可以找到最小节点。很多应用是针对这个功能的,它们并没有将拥有权完全转移给优先队列,而是把优先队列作为一个最小节点的选择器,从一堆节点中依次将它们选出来。这样一来节点的拥有权就可能同时被其它对象掌握。也就

19、是说某个节点虽不是最小节点,不能从优先队列那里“已知” ,但却可以从其它的拥有者那里“已知”。这种优先队列的应用也是很常见的。设想我们有一个闹钟,它可以记录很多个响铃时间,不过由于时间是线性的,铃只能一个个按先后次序响,优先队列就很适合用来作这样的挑选。另一方面使用者应该可以随时取消一个“已知”的响铃时间,这就需要进行任意已知节点的删除操作了。我们的这种删除操作需要指定被删除的节点,这和原来的删除根节点的操作是兼容的,因为根节点肯定是已知的。上面已经提过,在删除一个节点以后,将会剩下它的两棵子树,它们都是左偏树,我们先把它们合并成一棵新的左偏树。p Merge(left(x), right(x

20、)现在 p 指向了这颗新的左偏树,如果我们删除的是根节点,此时任务已经完成了。不过,如果被删除节点 x 不是根节点就有点麻烦了。这时 p 指向的新树的距离有可能比原来 x 的距离要大或小,这势必有可能影响原来 x 的父节点q 的距离,因为 q 现在成为新树 p 的父节点了。于是就要仿照合并操作里面的做法,对 q 的左右子树作出调整,并更新 q 的距离。这一过程引起了连锁反应,我们要顺着 q 的父节点链一直往上进行调整。x q q p 新树 p 的距离为 dist(p),如果 dist(p)+1 等于 q 的原有距离 dist(q),那么不管 p 是 q 的左子树还是右子树,我们都不需要对 q

21、进行任何调整,此时删除操第 11 页 共 23 页作也就完成了。如果 dist(p)+1 小于 q 的原有距离 dist(q),那么 q 的距离必须调整为 dist(p)+1,而且如果 p 是左子树的话,说明 q 的左子树距离比右子树小,必须交换子树。由于 q 的距离减少了,所以 q 的父节点也要做出同样的处理。剩下就是另外一种情况了,那就是 p 的距离增大了,使得 dist(p)+1 大于 q的原有距离 dist(q)。在这种情况下,如果 p 是左子树,那么 q 的距离不会改变,此时删除操作也可以结束了。如果 p 是右子树,这时有两种可能:一种是 p 的距离仍小于等于 q 的左子树距离,这时

22、我们直接调整 q 的距离就行了;另一种是 p 的距离大于 q 的左子树距离,这时我们需要交换 q 的左右子树并调整 q 的距离,交换完了以后 q 的右子树是原来的左子树,它的距离加 1 只能等于或大于 q 的原有距离,如果等于成立,删除操作可以结束了,否则 q 的距离将增大,我们还要对 q 的父节点做出相同的处理。删除任意已知节点操作的代码如下:Procedure Delete(x)q parent(x)p Merge(left(x), right(x)parent(p) qIf q NULL and left(q) = x Thenleft(q) pIf q NULL and right(q

23、) = x Thenright(q) pWhile q NULL DoIf dist(left(q) dist(q),那么 p 一定是 q 的左子树,否则会出现 q 的右子树距离缩小了,但是加 1 以后却大于 q 的距离的情况,不符合左偏树的性质 3。不论哪种情况,删除操作都可以结束了。注意到,每一次循环,p 的距离都会加 1,而在循环体内,dist(p)+1 最终将成为某个节点的距离。根据性质 4,第 12 页 共 23 页任何的距离都不会超过 logn,所以循环体的执行次数不会超过 logn。情况 2:p 的距离增大了。在这种情况下,我们将必然一直从右子树向上调整,直至 q 为空或 p 是

24、 q 的左子树时停止。一直从右子树升上来这个事实说明了循环的次数不会超过 logn(性质 4) 。最后我们看到这样一个事实,就是这两种情况只会发生其中一个。如果某种情况的调整结束后,我们已经知道要么 q 为空,要么 dist(p)+1 = dist(q),要么p 是 q 的左子树。这三种情况都不会导致另一情况发生。直观上来讲,如果合并后的新子树导致了父节点的一系列距离调整的话,要么就一直是往小调整,要么是一直往大调整,不会出现交替的情况。我们已经知道合并出新子树 p 的复杂度是 O(logn),向上调整距离的复杂度也是 O(logn),故删除操作的最坏情况的时间复杂度是 O(logn)。如果左

25、偏树非常倾斜,实际应用情况下要比这个快得多。3.6 小结本章介绍了左偏树的各种操作,我们可以看到,左偏树作为可并堆的实现,它的各种操作性能都十分优秀,且编程复杂度比较低,可以说是一个“性价比”十分高的数据结构。左偏树之所以是很好的可并堆实现,是因为它能够捕捉到具有堆性质的二叉树里面的一些其它有用信息,没有将这些信息浪费掉。根据堆性质,我们知道,从根节点向下到任何一个外节点的路径都是有序的。存在越长的路径,说明树的整体有序性越强,与平衡树不同(平衡树根本不允许有很长的路径) ,左偏树尽大约一半的可能保留了这个长度,并将它甩向左侧,利用它来缩短节点的距离以提高性能。这里我们不进行严格的讨论,左偏树

26、作为一个例子大致告诉我们:放弃已有的信息意味着算法性能上的牺牲。下面是最好的左偏树:有序表(插入操作是按逆序发生的,自然的有序性被保留了)和最坏的左偏树:平衡树(插入操作是按正序发生的,自然的有序性完全被放弃了) 。 第 13 页 共 23 页1234567 有序表 平衡树 1234567第 14 页 共 23 页四、左偏树的应用4.1 例 数字序列 (Baltic 2004)问题描述 *给定一个整数序列 a1, a2, , an,求一个不下降序列 b1 b2 bn,使得数列a i和b i的各项之差的绝对值之和 |a1 - b1| + |a2 - b2| + + |an - bn| 最小。数据

27、规模 1 n 10 6, 0 ai 2*109初步分析我们先来看看两个最特殊的情况:1a1a2an,在这种情况下,显然最优解为 bi=ai;2a1a2an,这时,最优解为 bi=x,其中 x 是数列 a 的中位数 。于是我们可以初步建立起这样一个思路:把 1n 划分成 m 个区间:q1,q2-1,q2,q3-1,qm,qm+1-1。每个区间对应一个解,bqi = bqi+1 = = bqi+1-1 = wi,其中wi 为 aqi, aqi+1, . , aqi+1-1 的中位数。显然,在上面第一种情况下 m=n,qi=i;在第二种情况下 m=1,q1=1。这样的想法究竟对不对呢?应该怎样实现?

28、若某序列前半部分 a1, a2, , an 的最优解为 (u,u,u),后半部分an+1, an+2, . , am 的最优解为 (v,v,v),那么整个序列的最优解是什么呢?若 uv,显然整个序列的最优解为 (u,u,u,v,v,v)。否则,设整个序列的最优解为 (b1,b2,bm),则显然 bnu(否则我们把前半部分的解 (b1,b2,bn) 改为 (u,u,u),由题设知整个序列的解不会变坏) ,同理 bn+1v。接下来,我们将看到下面这个事实:对于任意一个序列 a1,a2,an,如果最优解为 (u,u,u),那么在满足 uub1 或 bnuu 的情况下,( b1,b2,bn) 不会比

29、(u,u,u) 更优。我们用归纳法证明 uub1 的情况,bn uu 的情况可以类似证明。当 n=1 时,u=a1 ,命题显然成立。当 n1 时,假设对于任意长度小于 n 的序列命题都成立,现在证明对于长度为 n 的序列命题也成立。首先把 (b1, b2, bn) 改为 (b1, b1, b1),这一改动将不会导致解变坏,因为如果解变坏了,由归纳假设可知 a2,a3,an的中位数 wu,这样的话,最优解就应该为 (u,u,u,w,w,w),矛盾。然后我们再把( b1,b1,b1)改为 (u,u,u),由于 | a1 - x | + | a2 * 题目来源:Baltic OI 2004 Day

30、1, Sequence 本文对原题略微做了改动 为了方便讨论和程序实现,本文中提到的中位数,都是指数列中第 n/2 大的数 这里我们认为 qm+1 = n+1第 15 页 共 23 页- x | + + | an - x | 的几何意义为数轴上点 x 到点 a1, a2, an的距离之和,且 uub1,显然点 u到各点的距离之和不会比点 b1到各点的距离之和大,也就是说,( b1,b1,bn) 不会比 (v,v,v) 更优。 (证毕)再回到之前的论述,由于 bnu,作为上述事实的结论,我们可以得知,将 (b1,b2,bn)改为 (bn,bn,bn),再将 (bn+1,bn+2,bm)改为 (b

31、n+1,bn+1,bn+1),并不会使解变坏。也就是说,整个序列的最优解为 (bn,bn,bn,bn+1,bn+1,bn+1)。再考虑一下该解的几何意义,设整个序列的中位数为 w,则显然令 bn=bn+1=w 将得到整个序列的最优解,即最优解为 (w,w,w)。分析到这里,我们一开始的想法已经有了理论依据,算法也不难构思了。算法描述延续我们一开始的思路,假设我们已经找到前 k 个数 a1, a2, , ak (kwm+1,我们需要将最后两个区间合并,并找出新区间的最优解(也就是序列 a 中,下标在这个新区间内的各项的中位数) 。重复这个合并过程,直至 w1w2wm时结束,然后继续处理下一个数。

32、这个算法的正确性前面已经论证过了,现在我们需要考虑一下数据结构的选取。算法中涉及到以下两种操作:合并两个有序集以及查询某个有序集内的中位数。能较高效地支持这两种操作的数据结构有不少,一个比较明显的例子是二叉检索树(BST),它的询问操作复杂度是 O(logn),但合并操作不甚理想,采用启发式合并,总时间复杂度为 O(nlog2n)。有没有更好的选择呢?通过进一步分析,我们发现,只有当某一区间内的中位数比后一区间内的中位数大时,合并操作才会发生,也就是说,任一区间与后面的区间合并后,该区间内的中位数不会变大。于是我们可以用最大堆来维护每个区间内的中位数,当堆中的元素大于该区间内元素的一半时,删除

33、堆顶元素,这样堆中的元素始终为区间内较小的一半元素,堆顶元素即为该区间内的中位数。考虑到我们必须高效地完成合并操作,左偏树是一个理想的选择 *。左偏树的询问操作时间复杂度为 O(1),删除和合并操作时间复杂度都是 O(logn),而询问操作和合并操作少于 n 次,删除操作不超过 n/2 次(因为删除操作只会在合并两个元素个数为奇数的堆时发生) ,因此用左偏树实现,可以把算法的时间复杂度降为 O(nlogn)。小结这道题的解题过程对我们颇有启示。在应用左偏树解题时,我们往往会觉得题目无从下手,甚至与左偏树毫无关系,但只要我们对题目深入分析,加以适当的转化,问题终究会迎刃而解。这需要我们具有敏捷的

34、思维以及良好的题感。用左偏树解本题,相比较于前面 BST 的解法,时间复杂度和编程复杂度更* 前面介绍的左偏树是最小堆,但在本题中,显然只需把左偏树的性质稍做修改,就可以实现最大堆了第 16 页 共 23 页低,这使我们不得不感叹于左偏树的神奇威力。这不是说左偏树就一定是最好的解法,就本题来说,解法有很多种,光是可并堆的解法,就可以用多种数据结构来实现,但左偏树相对于它们,还是有一定的优势的,这将在下一章详细讨论。第 17 页 共 23 页五、左偏树与各种可并堆的比较我们知道,左偏树是一种可并堆的实现。但是为什么我们要用左偏树实现可并堆呢?左偏树相对于其他可并堆有什么优点?本章将就这个问题展开

35、讨论,介绍各种可并堆的特点,并对它们做出比较。5.1 左偏树的变种斜堆这里我们要介绍左偏树的一个变种斜堆(Skew Heap)。斜堆是一棵堆有序的二叉树,但是它不满足左偏性质,或者说,斜堆根本就没有“距离”这个概念它不需要记录任何一个节点的距离。从结构上来说,所有的左偏树都是斜堆,但反之不然。类似于左偏树,斜堆的各种操作也是在合并操作的基础上完成的,因此这里只介绍斜堆的合并操作,其他操作读者都可以仿照左偏树完成。斜堆合并操作的递归合并过程和左偏树完全一样。假设我们要合并 A 和 B两个斜堆,且 A 的根节点比 B 的根节点小,我们只需要把 A 的根节点作为合并后新堆的根节点,并将 A 的右子树

36、与 B 合并。由于合并都是沿着最右路径进行的,经过合并之后,新堆的最右路径长度必然增加,这会影响下一次合并的效率。为了解决这一问题,左偏树在进行合并的同时,检查最右路径节点的距离,并通过交换左右子树,使整棵树的最右路径长度非常小。然而斜堆不记录节点的距离,那么应该怎样维护最右路径呢?我们采取的办法是,从下往上,沿着合并的路径,在每个节点处都交换左右子树。下面是斜堆合并操作的代码:Function Merge(A,B)If A = NULL Then return BIf B = NULL Then return AIf key(B) 0)and(nda.key1)and(ndstkcl.keyndstkcl-1.key) dobegindec(cl);stkcl:=merge(stkcl,stkcl+1);if odd(qcl+1-qcl) and odd(qcl-qcl-1) thenstkcl:=merge(ndstkcl.left,ndstkcl.right);qcl:=qcl+1;end;end;print;close(output);EnD.

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 企业管理 > 管理学资料

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报