斐波那契堆

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

可合并堆可以实现以下操作:

  1. MAKE-HEAP ( ) \text {MAKE-HEAP}() MAKE-HEAP():创建一个新的可合并堆,堆中不含任何元素。
  2. INSERT ( H , x ) \text {INSERT}(H, x) INSERT(H,x):在可合并堆 H H H 中插入 x x x
  3. MININUM ( H ) \text{MININUM}(H) MININUM(H):返回堆 H H H 中的最小元素。
  4. EXTRACT-MIN ( H ) \text{EXTRACT-MIN}(H) EXTRACT-MIN(H):删除堆 H H H 中的最小元素。
  5. UNION ( H 1 , H 2 ) \text{UNION}(H_1,H_2) UNION(H1,H2):合并堆 H 1 H_1 H1 H 2 H_2 H2,并销毁堆 H 1 , H 2 H_1,H_2 H1,H2

斐波那契堆还支持以下两种操作:

  1. DECREASE-KEY ( H , x , k ) \text{DECREASE-KEY}(H,x,k) DECREASE-KEY(H,x,k):在斐波那契堆 H H H 中,修改 x x x 的关键字为 k k k,其中 k k k 需要小于 x x x 的原关键字。
  2. DELETE ( H , x ) \text{DELETE}(H,x) DELETE(H,x) 从堆 H H H 中删除元素 x x x

斐波那契堆和二叉堆的时间复杂度如下表所示:

操作斐波那契堆(摊还)二叉堆(最坏)
MAKE-HEAP O ( 1 ) O(1) O(1) O ( 1 ) O(1) O(1)
INSERT O ( 1 ) O(1) O(1) O ( lg ⁡ n ) O(\lg n) O(lgn)
MININUM O ( 1 ) O(1) O(1) O ( 1 ) O(1) O(1)
EXTRACT-MIN O ( lg ⁡ n ) O(\lg n) O(lgn) O ( lg ⁡ n ) O(\lg n) O(lgn)
UNION O ( 1 ) O(1) O(1) O ( n ) O(n) O(n)
DECREASE-KEY O ( 1 ) O(1) O(1) O ( lg ⁡ n ) O(\lg n) O(lgn)
DELETE O ( lg ⁡ n ) O(\lg n) O(lgn) O ( lg ⁡ n ) O(\lg n) O(lgn)

EXTRACT-MIN \text{EXTRACT-MIN} EXTRACT-MIN DELETE \text{DELETE} DELETE 操作数量比其他操作操作数量要小很多的时候,斐波那契堆尤其适用。

很多算法(例如最短路,最小生成树)中,对于很多边稠密图,每条边都调用 DECREASE-KEY \text{DECREASE-KEY} DECREASE-KEY,从二叉堆的 O ( lg ⁡ n ) O(\lg n) O(lgn),到 O ( 1 ) O(1) O(1) 是一个很大的改进。

所以像最短路这些算法,有些是可以用斐波那契堆加速的。

斐波那契堆

斐波那契堆是一些具有最小堆序的有根树的集合。(一大堆最小堆的集合)

每个节点 x x x 包含一个指向父亲的指针(可以用数组 f a t h e r [ x ] father[x] father[x],但指针可能好理解点) x . p x.p x.p 和指向他某个孩子(以后会知道有什么用) x . c h i l d x.child x.child

x x x 的所有孩子被链接的成一个环形双向链表,称这个链表为 x x x孩子链表 x x x 的一个孩子 y y y 有两个指针 y . l e f t y.left y.left y . r i g h t y.right y.right 分别指向左右兄弟。当 y y y 是仅有的孩子的时候, y . l e f t = y . r i g h t = y y.left = y.right = y y.left=y.right=y

环形双向链表应用在斐波那契堆中,组要有两个原因:

  1. 可以在 O ( 1 ) O(1) O(1) 的时间内从任意位置插入或删除一个节点
  2. 可以在 O ( 1 ) O(1) O(1) 时间内合并两个环形双向链表。

斐波那契堆中每个节点 x x x x . d e g r e e x.degree x.degree x x x 的度)表示节点 x x x 的孩子链表的大小(孩子的数量)。布尔值 x . m a r k x.mark x.mark 表示节点 x x x 在最近一次成为另一个节点的孩子后,有没有失去过孩子(还是一样的,后面就知道用途了)。

斐波那契堆 H H H 中, H . m i n H.min H.min 是一个指向斐波那契堆的具有最小关键字的节点的指针,我们将这个节点称为最小节点,如果斐波那契堆中没有节点, H . m i n = NIL H.min=\text{NIL} H.min=NIL H . n H.n H.n 表示当前斐波那契堆中节点个数。

斐波那契堆中,所有树的根都用 l e f t left left r i g h t right right 链成环形双向链表,这个链表被称为斐波那契堆的根链表。因此,指针 H . m i n H.min H.min 指向根链表中关键字最小的节点,根链表中的树次序可以任意。

势函数

我们将用势函数的方法来分析斐波那契堆的操作性能,对于一个斐波那契堆 H H H t ( H ) t(H) t(H) 表示 H H H 的根链表的元素个数,用 m ( H ) m(H) m(H) 表示 H H H 中已经标记的节点( x . m a r k x.mark x.mark t r u e true true 的节点)的数目。

则斐波那契堆 H H H 的势函数 Φ ( H ) \Phi(H) Φ(H) 如下:
Φ ( H ) = t ( H ) + 2 m ( H ) \Phi(H) = t(H) + 2m(H) Φ(H)=t(H)+2m(H)

最大度数

可合并堆操作

斐波那契堆上的一些可合并堆操作要尽可能长的延后执行。不同的操作可以进行性能平衡。

例如:如果从空的斐波那契堆开始,连续插入 k k k 个节点,这时候斐波那契堆是一个包含 k k k 个节点的环形双向链表。我们在斐波那契堆 H H H 上执行 EXTRACT-MIN \text{EXTRACT-MIN} EXTRACT-MIN 操作,移除 H . m i n H.min H.min 后,我们还需要 O ( k − 1 ) O(k-1) O(k1) 的时间去遍历根链表寻找 H . m i n H.min H.min。下面将看到,我们可以在执行 EXTRACT-MIN \text{EXTRACT-MIN} EXTRACT-MIN 之后,合并一些树以减小根链表规模。

创建一个新斐波那契堆

MAKE-FIB-NEAP \text {MAKE-FIB-NEAP} MAKE-FIB-NEAP 过程分配并返回一个斐波那契堆 H H H,其中 H . n = 0 H.n=0 H.n=0 H . m i n = HIL H.min=\text{HIL} H.min=HIL H H H 中不存在树, t ( H ) = m ( H ) = Φ ( H ) = 0 t(H)=m(H)=\Phi(H)=0 t(H)=m(H)=Φ(H)=0

插入一个节点

下面伪代码将节点 x 插入斐波那契堆 H H H 中,假设该节点已经被分配, x . k e y x.key x.key 已经被赋值。

FIB_HEAP_INSERT(H,x)
x.degree = 0//初始化x属性
x.p = NIL
x.child = NIL
x.mark = FALSE
if H.min == NIL//检测H是否为空
	create a root list for H containing just x
	H.min = x//使x称为H根链表中唯一节点,将H.min指向x
else insert x into H's root list
	if x.key < H.min.key
		H.min = x//更新H.min
H.n = H.n + 1

为确定 FIB-HEAP-INSERT \text{FIB-HEAP-INSERT} FIB-HEAP-INSERT 的摊还代价,设 H H H 是输入的斐波那契堆, H ′ H' H 是结果斐波那契堆。那么 t ( H ′ ) = t ( H ) + 1 t(H') = t(H) + 1 t(H)=t(H)+1 m ( H ′ ) = m ( H ) m(H') = m(H) m(H)=m(H),并且势的增加量为

( ( t ( H ) + 1 ) + 2 m ( H ) ) − ( t ( H ) + 2 m ( H ) ) = 1 ((t(H)+1)+2m(H))-(t(H)+2m(H)) = 1 ((t(H)+1)+2m(H))(t(H)+2m(H))=1

由于实际代价为 O ( 1 ) O(1) O(1),因此摊还代价为 O ( 1 ) + 1 = O ( 1 ) O(1) + 1 = O(1) O(1)+1=O(1)

寻找最小节点

斐波那契堆的最小节点可以直接通过指针 H . m i n H.min H.min 得到。由于 Φ ( H ) \Phi(H) Φ(H) 没有变化,所有时间复杂度为 O ( 1 ) O(1) O(1)

合并斐波那契堆

下面伪代码合并斐波那契堆 H 1 H_1 H1 H 2 H_2 H2,并在该过程中销毁 H 1 H_1 H1 H 2 H_2 H2。它简单的连接 H 1 H_1 H1 H 2 H_2 H2 的根链表的连接,任何确定新最小节点。

FIB_HEAP_UNION(H1,H2)
H = MAKE_FIB_HEAP() 
H.min = H1.min
concatenate the root list of H2 with the root list of H //链接H1,H2根链表
if(H1.min == NIL) or (H2.min != NIL ans H2.min.key < H1.min.key)
	H.min = H2.min //设定最小节点
H.n = H1.n + H2.n //计算H.n
return H//返回斐波那契堆H

势函数变化为:
Φ ( H ) − ( Φ ( H 1 ) + Φ ( H 2 ) ) = ( t ( H ) + 2 m ( H ) ) − ( ( t ( H 1 ) + 2 m ( H 1 ) ) + ( t ( H 2 ) + 2 m ( H 2 ) ) = 0 \Phi(H)-(\Phi(H_1)+\Phi(H_2))=(t(H)+2m(H))-((t(H_1)+2m(H_1))+(t(H_2)+2m(H_2)) = 0 Φ(H)(Φ(H1)+Φ(H2))=(t(H)+2m(H))((t(H1)+2m(H1))+(t(H2)+2m(H2))=0
所有 FIB-HEAP-UNION \text{FIB-HEAP-UNION} FIB-HEAP-UNION 的摊还代价等于实际代价 O ( 1 ) O(1) O(1)


部分参考于:
《算法导论》第19章 - 斐波那契堆

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值