算法导论 总结索引 | 第五部分 第十九章:斐波那契堆

1、斐波那契堆 有两种用途。第一种,它支持一系列操作,这些操作构成了“可合并堆”。第二种,斐波那契堆的一些操作 可以在常数摊还时间内完成,这使得这种数据结构 非常适合于 需要频繁调用这些操作的应用

2、可合并堆 是支持以下 5 种操作的一种数据结构,其中每个元素都有一个关键字:
MAKE-HEAP():创建和返回一个新的不含任何元素的堆
INSERT(H, x):将一个具有关键字的元素 x 插入堆 H 中
MINIMUM(H):返回一个指向堆 H 中具有最小关键字元素的指针
EXTRACT-MIN(H):从堆 H 中删除最小关键字的元素,并返回一个指向该元素的指针
UNION(H1, H2):创建并返回 一个包含堆 H1 和堆 H2 中所有元素的新堆。堆 H1 和堆 H2 由这一操作“销毁”

除了以上 可合并堆的操作外,堆还支持以下两种操作:
DECREASE-KEY(H, x, k):将堆 H 中元素 x 的关键字赋予新值 k。假定新值 k 不大于当前的关键字
DELETE(H, x):从堆 H 中删除元素 x

3、除了 UNION 操作外,二项堆的其他操作 均可在最坏情况时间为 O(lg n) 完成。 但是,如果需要支持 UNION ,则二项堆的性能就很差。通过把两个分别包含 要被合并二项堆的数组 进行链接,然后运行 BUILD-MIN-HEAP(6.3 节)的方式来实现 UNION 操作,其最坏情况下需要 Θ(n) 的时间

4、斐波那契堆对于 INSERT、 UNION 和 DECREASE-KEY,比二项堆有更好的渐进时间界,而对于剩下的几种操作,有相同的渐近运行时间。UNION 操作在斐波那契堆中 仅仅需要常数时间,这比二项堆的最坏情况下的线性时间要好很多

5、理论上的斐波那契堆 与 实际中的斐波那契堆
当 EXTRACT-MIN 和 DELETE 数目相对较少的时候,堆尤为适用。一些图问题算法 可能每条边调用一次 DECREASE-KEY。对于有很多边的稠密图,每次调用 DECREASE-KEY 需要 Θ(1) 摊还时间,相比起二项堆 最坏情况时间 Θ(lg n),计算最小生成树 和 寻找单源最短路径 的快速算法必不可少地要用到 斐波那契堆
在这里插入图片描述
7、除了某些需要管理大量数据的应用外,对于大多数应用,斐波那契堆的常数因子和编程复杂性 使得它比普通二项(或 k 项)堆并不那么适用

二项堆和斐波那契堆 对于 SEARCH 操作的支持均比较低效,涉及给定元素的操作(如 DECREASE-KEY 和 DELETE)均需要一个指针指向这个元素,并且指针 作为输入的一部分。当在应用中使用一个 可合并堆时,通常在可合并堆的每个元素中 存储一个句柄指向相关应用对象,同样 在每个应用对象中也存储一个句柄 指向可合并堆中相关元素

斐波那契堆 也是基于有根树的。把每一个元素表示成 树中的一个结点,每个结点具有一个 key 属性

1、斐波那契堆结构

1、一个斐波那契堆 是一系列 具有最小堆序的有根树的集合。每棵树 均遵循最小堆性质:每个结点的关键字 大于或等于 它的父结点的关键字
在这里插入图片描述
2、每个结点 x 包含 一个指向它父结点的指针 x.p 和一个指向它的某一个孩子的指针 x.child。x 的所有孩子链接成一个环形的双向链表,称为 x 的孩子链表。 孩子链表中的每个孩子 y 均有指针 y.left 和 y.right,分别指向 y 的左兄弟和右兄弟。如果 y 是仅有的一个孩子,则 y.left = y.right = y。孩子链表中 各兄弟出现的次序是任意的

环形双向链表 应用在斐波那契堆中有两个优点。第一,可以在 O(1) 时间内 从一个环形双向链表的任何位置插入一个结点 或 删除一个结点。第二,给定两个这种链表,可以用O(1)时间把它们链接成一个环形双向链表

3、把结点x的孩子链表中的孩子数目 储存在x.degree。布尔值属性 x.mark 指示结点x自从上一次成为另一个结点的孩子后,是否失去过孩子。新产生的结点 是未被标记的,并且 当结点x成为另一个结点的孩子时,它便成为未被标记结点。直到 DECREASE-KEY 操作,才把所有的mark属性值设为FALSE

通过指针 H.min 来访问一个给定的斐波那契堆H,该指针指向 具有最小关键字的树的根结点,把这个结点称为斐波那契堆的最小结点

所有树的根 都用其left和right指针链成一个环形的双链表,该双链表 称为斐波那契堆的根链表。根链表中的树次序可以任意

H.n,表示H中当前的结点数目

1.1 势函数

1、势方法来分析斐波那契堆操作的性能。用t(H)来表示H中 根链表中树的数目,用m(H)来表示 H中已标记的结点数目。然后,定义斐波那契堆H的势函数Φ(H)如下:
Φ(H) = t(H) + 2 m(H)
一系列斐波那契堆的势 等于各个斐波那契堆势的和

开始时,都没有堆。势初始值为0,势在 随后的任何时间内均不为负。对于某一操作序列来说,总的摊还代价的上界就是其总的实际代价的上界

1.2 最大度数

最大度数:对于摊还分析均假定:在一个n个结点的斐波那契堆中 任何结点的最大度数都有正的上界 D(n)(不证明这个假定)

如果仅仅是 支持可合并堆的操作,那么 D(n) ≤ ⌊lg n⌋。当支持 DECREASE-KEY 和 DELETE 操作时,也要求 D(n) = O(lg n)

2、可合并堆操作

1、斐波那契堆上的 一些可合并堆操作 要尽可能长地延后执行。如果在斐波那契堆 H 上执行一个 EXTRACT-MIN 操作,在移除 H.min 指向的结点后,将不得不遍历根链表中 剩下的 k-1 个结点来找出新的最小结点,这里存在性能平衡问题。只要 在执行 EXTRACT-MIN 操作中遍历整个根链表,并且 把节点合并到 最小堆序树中以减小根链表的规模
不论根链表 在执行 EXTRACT-MIN 操作之前是什么样子,执行完该操作之后,根链表中的每个节点 要求有一个与根链表中其他节点均不同的度数,这使得根链表的规模最大是 D(n)+1
由于根链表中的每个节点的度数必须互不相同,且每个度数从 0 到 D(n) 各有一个节点,因此根链表中的节点数最多为 D(n)+1

2、在执行 EXTRACT-MIN 操作时,堆中的最小节点被移除,其子节点会被分裂并成为新的根节点,加入到根链表中。最大度数 D(n) 可以理解为满足斐波那契数列增长的特性,即 D(n) 是与堆的大小 n 有关的一个对数级别的函数,通常 D(n) 是 O(logn)

2.1 创建一个新的斐波那契堆

1、MAKE-FIB-HEAP 过程分配并返回一个斐波那契堆对象 H,其中 H.n=0 和 H.min=NIL,H 中不存在树。因为 t(H)=0 和 m(H)=0,空斐波那契堆的势为 Φ(H)=0。因此,MAKE-FIB-HEAP 的摊还代价等于它的实际代价 O(1)

2.2 插入一个结点

1、假设该节点已经被分配,x.key 已经被赋值

FIB-HEAP-INSERT(H,x)
1 x.degree = 0
2 x.p = NIL
3 x.child = NIL
4 x.mark = FALSE
5 if H.min == NIL
6 	create a root list for H containing just x
7 	H.min = x
8 else insert x into H's root list
9 	if x.key < H.min.key
10 		H.min = x
11 H.n = H.n + 1

将节点 x 插入 H 的根链表中,如果有必要,就更新 H.min
在这里插入图片描述
为了确定 FIB-HEAP-INSERT 的摊还代价,设 H 是输入的斐波那契堆,H’ 是结果斐波那契堆。那么 t(H’)=t(H)+1 和 m(H’)=m(H),并且势的增加量为:
((t(H)+1)+2m(H))-(t(H)+2m(H))=1
由于实际代价为 O(1),因此摊还代价为 O(1)+1=O(1)

2.3 寻找最小结点

斐波那契堆的最小结点 可以通过指针 H.min 得到。因此,可以在 O(1) 的实际代价找到最小结点。由于 H 的势没有发生变化,因此该操作的摊还代价 等于它的实际代价 O(1)

2.4 两个斐波那契堆的合并

在该过程中销毁 H1 和 H2。它简单地将 H1 和 H2 的根链表链接,然后确定新的最小结点。之后,表明 H1 和 H2 的对象将不再使用

FIB-HEAP-UNION(H1,H2)
1 H = MAKE-FIB-HEAP()
2 H.min = H1.min
3 concatenate the root list of H2 with the root list of H
4 if (H1.min == NIL or H2.min !=NIL and H2.min.key < H1.min.key)
5 	H.min = H2.min
6 H.n = H1.n + H2.n
7 return H

势函数的变化:

Φ(H) = (Φ(H1) + Φ(H2)) = (t(H) + 2m(H)) - ((t(H1) + 2m(H1)) + (t(H2) + 2m(H2))) = 0

因为 t(H) = t(H1) + t(H2) 和 m(H) = m(H1) + m(H2),所以 FIB-HEAP-UNION 的摊还代价等于它的实际代价 O(1)

2.5 抽取最小结点

为了简便该代码,假定当一个结点从链表中移除后,留在链表中的指针要被更新,但是抽取出来的结点中的指针并不改变

FIB-HEAP-EXTRACT-MIN(H)
1 z=H.min
2 if z!=NIL
3 	for each child x of z
4 		add x to the root list of H
5 		x.p=NIL
6 	remove z from the root list
7 	if z==z.right // 右兄弟指针指向自身,说明树空了,只有z一个根结点
8 		H.min=NIL
9 	else H.min=z.right 
10 		CONSOLIDATE(H)
11 	H.n=H.n-1
12 return z

首先 将最小结点的每个孩子变为根结点,并从根链表中删除该最小结点。然后通过 把具有相同度数的根结点 合并的方法来链接成根链表,直至 每个度数至多只有一个根在链表中(每棵树的度数都不同)

执行完第6行之后,如果 z 是它自身的右兄弟,那么 z 是根链表中仅有的一个结点 并且它没有孩子结点(有的话会加入到 根列表中),第8行使斐波那契堆成为空堆。否则,把指针 H.min 指向根链表中除 z 之外的某个根结点(这里是 z 的右兄弟),该根结点没有必要一定是在 FIB-HEAP-EXTRACT-MIN 执行完后的新的最小结点。图 19-4(b) 所示的是图 19-4(a) 执行完第 9 行之后的斐波那契堆

下一步是合并 H 的根链表,通过调用 CONSOLIDATE(H) 来减少斐波那契堆中 树的数目。合并根链表的过程为 重复执行以下步骤,直到根链表中的每一个根有不同的度数

  1. 在根链表中找到 两个具有相同度数的根 x 和 y 。假设 x.key ≤ y.key
  2. 把 y 链接到 x:从根链表中移除 y,调用 FIB-HEAP-LINK 过程,使 y 成为 x 的孩子。该过程将 x.degree 属性增 1,并清除 y 上的标记

过程 CONSOLIDATE 使用一个辅助数组 A[0…D(H.n)] 来记录根结点对应的度数的轨迹。如果 A[i] = y,那么当前的 y 是一个具有 y.degree = i 的根

CONSOLIDATE(H)
1 let A[0...D(H.n)] be a new array
2 for i = 0 to D(H.n)
3 	A[i]=NIL
4 for each node w in the root list of H
5 	x = w
6 	d = x.degree
7 	while A[d] ≠ NIL
8 		y = A[d] // another node with the same degree as x
9 		if x.key > y.key
10 			exchange x with y
11 		FIB-HEAP-LINK(H,y,x)
12 		A[d] = NIL
13 		d = d+1
14 	A[d] = x
15 H.min = NIL
16 for i = 0 to D(H.n)
17 	if A[i] ≠ NIL
18 		if H.min == NIL
19 			create a root list for H containing just A[i]
20 			H.min = A[i]
21 		else insert A[i] into H's root list
22 			if A[i].key < H.min.key
23 				H.min = A[i]   // 15-23行是寻找 H.min 的过程

FIB-HEAP-LINK(H, y, x)
remove y from the root list of H
make y a child of x, incrementing x.degree
y.mark = FALSE

因为 想要每个根都有不同的度数,所以查找数组 A 来确定 是否有某个根 y 与 x 有相同度数。如果有,则把根 x 和 y 链接起来,并保证链接完成后 x 仍然是一个根。也就是说,如果 y 的关键字小于 x 的关键字,则先交换指向这两个根的指针,再把 y 链接到 x (x始终是根),x 的度数增加 1,继续执行这个过程,把 x 和另一个与 x 的新度数相同的根链接,直到处理过的根没有与 x 有相同的度数。然后,将 A 的相关元素指向 x。这样处理后续根时,已经记录 x 是已处理过的根中 有该度数的唯一根

第 7~13 行的 while 循环反复地 将包含结点 w 的以 x 为根的树 与 和 x 度数相同的根相连接,直到没有其他的根与 x 有相同的度数。这个 while 循环维持了如下不变式:
在 while 循环的每次迭代开始处,d=x.degree
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
证明从一个 n 个结点的斐波那契堆中 抽取最小结点的摊还代价为 O(D(n))。设 H 表示执行 FIB-HEAP-EXTARCT-MIN 操作之前的斐波那契堆

首先 给出抽取最小结点的实际代价。FIB-HEAP-EXTARCT-MIN 最多处理 最小结点的 D(n) 个孩子,再加上 CONSOLIDATE 中第 2~3 行和第 16~23 行的工作,合计需要的时间代价为 O(D(n))

剩下的是分析 CONSOLIDATE 中第 4~14 行的 for 循环代价,这一部分我们使用聚合分析。因为原始的根链表中有 t(H) 个结点,减去抽取的结点(FIB-HEAP-EXTARCT-MIN中),再加上抽取的结点的孩子结点(至多为 D(n)),所以调用 CONSOLIDATE 时根链表的大小最多为 D(n)+t(H)-1。给定第 4~14 行 for 循环次数取决于根链表,但是我们知道每次调用 while 循环,总有一个根结点被链接到另一个上,因此 for 循环的总工作量 最多与 D(n) + t(H) 成正比。所以,抽取最小结点的总实际工作量为 O(D(n)+t(H))

抽取最小结点之前的势为 t(H)+2m(H),因为最多有 D(n)+1 个根留下(对于每一个可能的度数 i (0 <= i <= D(n)),最多只有一个根节点具有这个度数) 且在这个过程中没有任何结点被标记,所以在该操作之后势最大为 (D(n)+1)+2m(H)

所以摊还代价最多为(操作代价 + 势):
在这里插入图片描述
由于 每次链接操作均把根的数目减小 1,因此每次链接操作的代价 可以用势的减小来支付。D(n)=O(lg n),因此抽取最小结点的摊还代价为 O(lg n)

3、关键字减值和删除一个结点

1、如何在 O(1) 的摊还时间内 减小斐波那契堆中某个结点的关键字的值,以及 如何在 O(D(n)) 的摊还时间从一个 n 个结点的斐波那契堆中删除一个结点

3.1 关键字减值

FIB-HEAP-DECREASE-KEY(H,x,k)
1 if k > x.key
2 	error "new key is greater than current key"
3 x.key = k
4 y = x.p
5 if y != NIL and x.key < y.key
6 	CUT(H,x,y)
7 	CASCADING-CUT(H,y)
8 if x.key < H.min.key
9 	H.min = x

CUT(H, x, y)
remove x from the child list of y, decrementing y.degree
add x to the root list of H
x.p = NIL 
x.mark = FALSE

CASCADING-CUT(H,y)
1 z = y.p
2 if z != NIL
3 	if y.mark == FALSE
4 		y.mark = TRUE
5 else CUT(H,y,z)
6 		CASCADING-CUT(H,z)

1、如果违反了最小堆序,则需要进行很多改变。首先在第 6 行切断 x。CUT 过程“切断”x 与其父结点 y 之间的链接,使 x 成为根结点

mark 属性来得到所需的时间界。该属性记录了每个结点的一小段历史。假设下面的步骤已经发生在结点 x 上:

  1. 在某时刻,x 是根。
  2. 然后 x 被链接到另一个结点(成为孩子结点)
  3. 然后 x 的两个孩子被切断操作移除

一旦失掉 第二个孩子,就切断 x 与其父结点的链接,使其成为一个新的根。如果发生第 1 步和第 2 步且 x 的一个孩子被切掉,那么属性 x.mark 为 TRUE。因此,由于 CUT 过程执行了第 1 步,所以它在第 4 行清除 x.mark(x.mark = false)。(知道了为什么 FIB-HEAP-LINK 中第 3 行清除 y.mark:因为结点 y 还被链接到另一个结点上,即上面的第 2 步正确执行。下一次如果 y 的 一个孩子被切掉,则 y.mark 将被设为 TRUE。)

因为 x 可能是其父结点 y 被链接到另一个结点后 被切掉的第二个孩子。因此,FIB-HEAP-DECREASE-KEY 的第 7 行试图在结点 y 上执行一级联切断(cascading-cut)操作。如果 y 是根结点,那么返回。如果 y 是未标记的结点,既然 它的第一个孩子已经被切掉(前面执行过 cut),那么该过程 在第 4 行标记它,并返回
然而,如果 y 是被标记过的,那么 y 刚刚失去了它的第二个孩子 z,所以 y 在第 5 行被切掉(调用 cut),且第 6 行 CASCADING-CUT 递归调用它本身来处理 y 的父结点 z。CASCADING-CUT 过程沿着树一直递归向上,直到它遇到根结点或者一个未被标记的结点

一旦 所有的级联切断都完成,如果有必要,FIB-HEAP-DECREASE-KEY 的第 8~9 行更新 H.min,然后结束程序。唯一一个关键字发生改变的结果是 关键字减小的结果 x。因此,新的最小结果 要么是原来的最小结点,要么是结点 x

2、CASCADING-CUT 是一种更复杂的操作,它不仅可能切断一个节点 y 与其父节点的连接(CUT函数干的),还会检查 y 的父节点是否已经被标记,如果已被标记,则向上递归地 进行级联切断。这是为了维护斐波那契堆的两个重要性质:标记规则和树的结构

标记规则确保了在斐波那契堆中,除了根节点之外,每个节点最多只能失去一个孩子而不会被再次标记,除非它被切断并移动到根列表中。这样做的目的是为了 限制在减少键值或删除操作中可能发生的切断次数,从而保持良好的平均时间复杂度

为何限制切断次数能保持良好平均时间复杂度:
每次切断操作都需要更新数据结构的状态,比如重新链接节点到根列表,更新最小元素指针等。这些操作本身的时间复杂度并不高,但是频繁的切断会增加总的操作成本,因为每个节点在被切断并移到根链表中之前,最多只能被标记一次。这意味着一个节点至多只会触发一次级联切断操作

3、初始的斐波那契堆如图 19-5(a) 所示。图 19-5(b) 所示的是第一次调用,其中不涉及任何级联切断。图 19-5©~(e) 所示的是第二次调用,其中引发了两次级联切断
(黑色的结点 为被标记的结点)
在这里插入图片描述
图 19-5:FIB-HEAP-DECREASE-KEY 的两次调用。(a) 初始的斐波那契堆。(b) 关键字为 46 的结点将关键字减少到 15。该节点成为根结点,它的父结点(具有关键字 24)之前没有被标记,现在被标记了。
©~(e) 关键字为 35 的结点将关键字减小到 5。该结点变为根结点,它的父结点(关键字为 26)被标记过。因此需要调用一个级联切断操作。关键字为 26 的结点被从父结点剪切下来成为图(d)的一个未被标记的。另一个级联切断操作需要执行,因为关键字为 24 的结点也被标记。该结点被从它的父结点剪切下来,成为图(e)中的一个未被标记的根。因为关键字为 7 的结点是个根,所以级联切断操作在此结束。(即使这个结点不是根,级联操作也会结束,因为它未被标记。)图(e)展示了 FIB-HEAP-DECREASE-KEY 操作后的结果,其中 H.min 指向了新的最小结点

4、现在来证明 FIB-HEAP-DECREASE-KEY 的摊还代价为O(1)。先来推导它的实际代价。FIB-HEAP-DECREASE-KEY 调用中,要调用 c 次 CASCADING-CUT(递归调用)。CASCADING-CUT 的每一次调用 (不包括递归调用) 需要 O(1) 的时间。因此,包含所有的递归调用后,FIB-HEAP-DECREASE-KEY 的实际代价为O(c)

计算势的变化。设H是 FIB-HEAP-DECREASE-KEY 操作执行之前的斐波那契堆。除了最后一次调用外,其他每一次调用 CASCADING-CUT 均切掉一个标记得过的结点,并清除该结点的标记位
除了最后一次调用,其他每一次调用 CASCADING-CUT ,均切掉 一个标记过的结点 并清除该结点的标记位

此后,斐波那契堆包含 t(H)+c 棵树 (原来的 t(H) 棵树,c-1 棵被级联切断操作产生的树,以及 以结点x为根的树(切完正好剩自己)),而且 最多有 m(H) - c + 2 个被标记的结点 (c-1 个结点被级联切割操作清除标记,最后一次调用CASCADING-CUT 可能又标记了一个结点)。因此势的变化最多为:

((t(H) + c) + 2(m(H) - c + 2)) - (t(H) + 2m(H)) = 4 - c

因此,FIB-HEAP-DECREASE-KEY的摊还代价至多是

O(c) + 4 - c = O(1)

为什么 在定义势函数时,要包含 一个2倍于标记结点数目的项。当一个标记的结点 y 被一个级联切割操作切掉时,它的标记位被清空(标记结点数 - 1),这使得势减小2。一个单位的势支付 切断和标记位的清除(前面得出 FIB-HEAP-DECREASE-KEY的摊还代价至多是 O(1)),另一个单位 补偿了因为结点 y 变成根而增加的势(多了树)

3.2 删除一个结点

1、在 O(D(n)) 的摊还时间内 从一个具有n个结点的斐波那契堆中删除一个结点。假定在斐波那契堆中 任何关键字的当前值均不为 -∞

FIB-HEAP-DELETE(H, x)
    FIB-HEAP-DECREASE-KEY(H, x, -)
    FIB-HEAP-EXTRACT-MIN(H)

FIB-HEAP-DELETE 把唯一的最小关键字 -∞ 赋予x,使 x 变为斐波那契堆中最小的结点。然后 FIB-HEAP-EXTRACT-MIN 过程 从斐波那契堆中移除。FIB-HEAP-DELETE 的摊还时间为 FIB-HEAP-DECREASE-KEY 的O(1) 摊还时间 和 FIB-HEAP-EXTRACT-MIN 的 O(D(n)) 摊还时间之和

2、假定斐波那契堆的一个根x被标记了。解释x是如何成为一个被标记的根的。试说明是否被标记对分析并没有影响,即使它不是一个先被链接到另一个结点,后又丢失了一个孩子的根 x 成为一个被标记的根是因为它曾经是 H.min 的一个被标记的子节点,而 H.min 在执行 FIB-HEAP-EXTRACT-MIN 操作时被移除了
在这里插入图片描述

键为 18 的节点成为了被标记的根。这并不会增加 x 被标记的潜在性,因为唯一检查标记状态的时间是在级联切割的第 3 行。然而,这仅适用于父节点非 NIL 的节点。由于每个根的父节点都是 NIL

CASCADING-CUT(H,y)
1 z = y.p
2 if z != NIL
3 	if y.mark == FALSE
4 		y.mark = TRUE
5 else CUT(H,y,z)
6 		CASCADING-CUT(H,z)

级联切割的第 3 行永远不会在这个被标记的根上运行。这将使得势函数比需要的更大,但为了使势函数变得更高而付出的额外计算 将在以后永远不会被使用

3、使用聚合分析来证明 FIB-HEAP-DECREASE-KEY 的 O(1) 摊还时间是每个操作的平均代价
需要计算所有这些操作的实际总成本。由于 FIB-HEAP-DECREASE-KEY 操作可能触发级联切断(cascading cut),总的实际成本 可能比简单的 O(1) 成本更高。但是,我们知道级联切断的频率受到限制:每个节点在其整个生命周期中最多被切断两次(第一次失去孩子时不被切断,第二次失去孩子时才被切断并加入根列表)。这意味着,即使在最坏的情况下,级联切断的总次数也不会超过堆中节点的两倍

假设每个FIB-HEAP-DECREASE-KEY操作的最坏情况成本为 O(c),其中c是级联切断的次数。然而,由于每个节点最多只被切断两次,整个序列的总成本是O(n)(每个节点的O(1)基本成本加上O(n)的级联切断成本)
既然我们知道了整个序列的总成本是O(n),那么将这个成本平均到n个操作上,我们得到每个操作的平均成本为O(n)/n = O(1)

这种方法强调了斐波那契堆设计的精妙之处:通过限制级联切断的频率,即使单个操作的成本可能波动,整体的平均成本仍然保持在可接受的范围内,这使得斐波那契堆在处理大量连续操作时非常高效

4、最大度数的界

1、要证明 FIB-HEAP-EXTRACT-MIN 和 FIB-HEAP-DELETE 的摊还时间为 O(lg n),必须证明 一个具有 n 个结点的斐波那契堆中 任意结点的度数的上界 D(n) 为 O(lg n)。特别的,要证明 D(n) ≤ [logφn],这里φ是黄金分割率:
φ=(1+√5)/2=1.6180…
定义 size(x) 为以 x 为根的子树中包括 x 本身在内的结点个数,将证明 size(x) 是 x.degree 的幂

2、设 x 是斐波那契堆中的任意结点,并假定 x.degree = k。令 y1, y2, …, yk 表示 x 的孩子,并以它们链入 x 的先后顺序排列,则 y1.degree ≥ 0,且对于 i = 2, 3, …, k,有 y_i.degree ≥ i−2

证明:y_1.degree ≥ 0。对于 i >= 2,当 y_i 被链入 x 的时候,y_1,y_2,…,y_i-1 已经是 x 的孩子,因此一定有 x.degree >= i - 1。因为结点 y_i 只有在 x.degree = y_i.degree 的时候才会被链入x(执行操作 COSOLIDATE),此时也一定有 y_i.degree ≥ i−1。从这之后,结点 y_i 最多失去一个孩子,因为它失去了两个孩子,它将被从 x 中剪切掉(执行操作CASCADING-CUT)。综上,y_i.degree ≥ i−2

3、斐波那契堆中的斐波那契:
对于 k = 0, 1, 2, …, 第 k 个斐波那契数 定义为如下递归式
在这里插入图片描述
另一种表达 F_k 的方法:
对于所有的整数 k ≥ 0,(1)
在这里插入图片描述
在这里插入图片描述
对于所有的整数 k ≥ 0,斐波那契数的第 k + 2 个数满足 F_k+2 >= ϕk
证明:对其进行归纳。归纳基础是 k = 0 和 k = 1 的情形。当 k = 0 时,有 F_2 = 1 = ϕ0,并且当 k = 1 时,有
F_3 = 2 > 1.619 > ϕ0。归纳步是对于 k ≥ 2,假定对于 k = 0, 1, …, k - 1,有 F_i+2 > ϕi,ϕ 是等式 (3.23) x2 = x + 1 定义的正根。因此,有 (2)
在这里插入图片描述
设 x 是斐波那契堆中的任意结点,并设 k = x.degree,则有 size(x) ≥ F_k+2 ≈ ϕk

证明 设 s_k 表示斐波那契堆中度数为 k 的任意结点的 最小可能 size。平凡地,s_0 = 1,s_1 = 2,…,s_k 最大为 size(x),因为往一个结点上 添加孩子不能减少该结点的 size,s_k 随着 k 单调递增
考虑某个结点 z,有 z.degree = k 且 size(z) = s_k。因为 s_k ≤ size(x),所以可以通过计算 s_k 的下界来得到 size(x) 的一个下界
用 y_1,y_2,…,y_k 表示结点 z 的孩子,并按照它们链入该结点的先后顺序排列。为了求 s_k 的界,把 z 本身和 z 的第一个孩子 y_1 (size(y_1) ≥ 1) 各算一个(2的由来),则有
在这里插入图片描述
其中最后一行由 上文得到(因此有 y_i.degree ≥ i-2),以及 s_k 的单调性(因此有 s_y_i.degree ≥ s_i-2)得到

对 k 进行归纳证明,对于所有的非负整数 k,有 s_k ≥ F_k+2 (与斐波那契产生联系)
归纳基础,k = 0 和 k = 1 时显然成立。对于归纳步,假设 k ≥ 2 且对于 i = 0, 1, …, k-1,均有 s_i ≥ F_i+2,则有
在这里插入图片描述
19.2 就是(1),19.3 就是(2)
得证:size(x) ≥ F_k+2 ≈ ϕk
特定度(k)的树(或子树)的根结点数量(size(x))的下界 就是斐波那契数列(F_k+2)

斐波那契数列的增长率与黄金分割比 ϕ≈1.618 相关,这意味着随着 k 的增加,s_k 的增长率会变得非常快

4、一个n个结点的斐波那契堆中任意结点的最大度数 D(n) 为 O(lg n)
证明 设 x 是一个 n 个结点的斐波那契堆中的任意结点,并设 k = x.degree。有 n ≥ size(x) ≥ φk。取 φ 为底的对数,得到 k ≤ logφn。(实际上,因为 k 是整数,所以 k ≤ ⌊logφn⌋) 所以,任意结点的最大度数 D(n) 为 O(lg n)

  • 12
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值