斐波那契堆的C++实现

本文改编自《算法导论》第三版第19章:斐波那契堆。文中代码为书中伪代码的C++实现,如有疏漏还请指出。

1.斐波那契堆的结构

斐波那契堆是一种可并堆,它支持以下操作:
( 1 ) (1) (1) O ( 1 ) O(1) O(1)时间内插入元素、获取最小值、合并两个斐波那契堆、减小元素键值;
( 2 ) (2) (2) O ( l o g n ) O(logn) O(logn)时间内删除元素。
斐波那契堆的平摊复杂度比其他可并堆(左偏树/二项队列)要优,但时空常数大且实现复杂度高,在实际中并不非常适用。
斐波那契堆有下图所示的结构(图示为小根堆):
在这里插入图片描述
可以看到斐波那契堆仍然满足小根堆性质,与二叉堆不同的是,斐波那契堆的第一层由双向循环链表组成(23和24的连接没有画出),并且每一个结点的孩子集合也由双向循环链表维护。(如:3的孩子为{18,53,38},这三个结点在一个双向循环链表中,3只存指向其中一个的指针)
每个结点有如下属性(成员变量):

template<typename T>
struct node
{
    
    
	T data;
	bool mark;
	int degree;
	node *left,*right;
	node *child,*parent;
};

d a t a : data: data:数据域;
m a r k : mark: mark:标记, m a r k = = t r u e mark == true mark==true当且仅当某个结点成为某个节点的子结点后还失去了某儿子结点,黑色节点表示 m a r k = t r u e mark = true mark=true
d e g r e e : degree: degree:孩子个数;
l e f t , r i g h t : left,right: left,right:左右兄弟;
c h i l d : child: child:任意的一个该结点孩子;
p a r e n t : parent: parent:该结点的父节点。
每一层结点由双向循环链表构成,为了实现方便在程序中引入表头。

斐波那契堆的成员变量:

int size;//结点个数
node *min;//最小元素指针
node *head;//第一层元素的链表表头

2.斐波那契堆操作及实现

2.1插入节点:

void LeftInsert(node *base, node *tmp)
{
    
    //LeftInsert:在双向循环链表中把tmp插入base左边
	tmp->left = base->left;
	tmp->right = base;
	base->left->right = tmp;
	base->left = tmp;
}
void Insert(T data)
{
    
    
	//初始化
	node *tmp = new node;
	tmp->degree = 0;
	tmp->data = data;
	tmp->parent = tmp->child = nullptr;
	tmp->left = tmp->right = tmp;//双向循环链表需要把左右指针指向自己
	tmp->mark = false;
	if(min == nullptr)//空堆,min指针指向该结点
	{
    
    
		min = tmp;
		LeftInsert(head, min);//LeftInsert定义见上方
	}
	else
	{
    
    
		LeftInsert(min, tmp);
		if(data < min->data)//插入的结点更小,min指向新结点
			min = tmp;
	}
	size++;
}

显然,斐波那契堆的插入操作为常数复杂度(虽然该常数较大)。

2.2合并两个斐波那契堆

FibHeap<T> Merge(FibHeap<T> H2)
{
    
    
	node *ptr = head->right;
	do
	{
    
    
		H2.Insert(ptr->data);
		ptr = ptr->right;
	}while(ptr != head);
	if(H2.min == nullptr || (min != nullptr && min->data < H2.GetMin()))
		H2.min = min;
	return H2;
}

对于每个第一层结点 x x x,把 x x x放入 H 2 H_2 H2的第一层结点中,同时在有必要时改变min指针。

2.3删除最小结点

T DeleteMin()
{
    
    
	T ret = min->data;//保存返回的最小值
	node *ptr = min;
	if(ptr != nullptr)
	{
    
    
		node *tmp = min->child,*nxt;
		for(int i = 0; i < min->degree; i++)
		{
    
    
			nxt = tmp->right;
			LeftInsert(head, tmp);
			tmp = nxt;
		}//由于在LeftInsert之后tmp的链会断,所以需要提前记录下一个孩子
		ptr->left->right = ptr->right;
		ptr->right->left = ptr->left;//在第一层链表中把min去掉

		if(ptr == ptr->right)//此时堆中仅有一个元素
			min = nullptr;
		else
		{
    
    
			min = min->right;//先随意指向一个结点
			if(min == head) min = min->right;//指向表头,无意义,再指向下一个
			Consolidate();//调整堆结构
		}
		size--;
	}
	return ret;
}

要删除一个最小结点,首先将min指针指向的结点的所有孩子结点加入堆的第一层结点链表中,然后删除min结点(但保留其left,right结构),随后调整min指针的位置,但不要求其一定指向新的最小结点,这一步在 C o n s o l i d a t e Consolidate Consolidate(合并)函数中完成。

Consolidate函数

Consolidate()负责调整堆的结构,使第一层结点数减小,同时确定新的最小值位置。

void link(node *y,node *x)//把y插入到x下面
{
    
    
	y->left->right = y->right;
	y->right->left = y->left;
	y->parent = x;
	if(x->child == nullptr)//当x没有儿子
	{
    
    
		x->child = y;
		y->left = y->right = y;
	}
	else LeftInsert(x->child, y);
	x->degree++;
	y->mark = false;//把标记清空,由于它刚刚成为子结点,仍需失去儿子结点mark才会标为true
}
void Consolidate()
{
    
     
	int Dn = (int)log2(size)+1;
	node **nodes = new node*[Dn];
	for(int i = 0; i < Dn; i++)
		nodes[i] = nullptr;
	node *ptr = head->right,*nxt;
	do
	{
    
    
		int d;
		node *x,*y;
		x = ptr;
		if(x == head) continue; 
		d = x->degree;
		nxt = ptr->right;
		while(nodes[d] != nullptr)
		{
    
    
			y = nodes[d];
			if(x->data > y->data)
				std::swap(x, y);
			link(y,x);
			nodes[d] = nullptr;
			d++;
		}
		nodes[d] = x;
		ptr = nxt;
	}while(ptr != head);
	min = nullptr;
	head->left = head->right = head;
		for(int i = 0; i < Dn; i++)
		{
    
    
			if(nodes[i] != nullptr)
			{
    
    
				if(min == nullptr)
				{
    
    
					min = nodes[i];
					LeftInsert(head,min);
				}
			else
			{
    
    
				LeftInsert(head, nodes[i]);
				if(nodes[i]->data < min->data)
					min = nodes[i];
			}
		}
	}
}

首先(程序的do-while循环以及之前)建立一个指针数组 n o d e s [ ] nodes[] nodes[],其大小为 D ( n ) + 1 D(n)+1 D(n)+1, D ( n ) D(n) D(n)为当前斐波那契堆结点的最大度数。最后(第4部分)会证明, D ( n ) = D(n)= D(n)= O ( l o g n ) O(logn) O(logn),更具体地有 D ( n ) ≤ ⌊ l o g 2 x ⌋ D(n)\le \lfloor log_2x \rfloor D(n)log2x n o d e s [ i ] nodes[i] nodes[i]指向一个度数为 i i i的结点。要完成整理,对于每个结点,设其度为 d . d. d.若当前 n o d e s [ d ] nodes[d] nodes[d]已经指向另一个结点,那么比较这两个结点,让根元素小的结点成为根元素大结点的父亲(即link操作),同时根小的结点的度数由于获得了一个儿子 + 1 ( d + + ) +1(d++) +1(d++).重复操作直到找到一个 n o d e s [ d ] nodes[d] nodes[d]为空,可以安放当前处理的结点,将 n o d e s [ d ] nodes[d] nodes[d]指向当前节点。

在程序实现方面,注意(1)跳过表头:引入表头是为了确定一个基准,由于在调整过程中有的结点会成为其他结点的儿子,导致无法判断循环结束条件,表头可以提供一个基准;(2)在处理link时会导致一个结点断链,从而丢失要遍历的下一个结点,这时要用一个next指针在link之前标记出下一个要遍历的结点。

在堆的重构完成后,需要确立新的最小值结点。与插入类似,只需要将 n o d e s [ ] nodes[] nodes[]下挂着的所有结点插入到堆中即完成整理。(同时不断更新最小值指针)
下图展示了删除最小值的过程:
(1)原斐波那契堆:
在这里插入图片描述

(2)执行DeleteMin()中,到Consolidate()之前的语句:(把儿子插入到第一层链表)
在这里插入图片描述
(3)开始执行Consolidate():
(i)从17开始向右遍历,由于 n o d e s [ 0 , 1 , 2 ] nodes[0,1,2] nodes[0,1,2]均为空,分别指向23,17,24。这里用到了 D n ≤ ⌊ l o g n ⌋ Dn \le \lfloor logn \rfloor Dnlogn的结论。注意数组大小应为 D ( n ) + 1. D(n)+1. D(n)+1.
在这里插入图片描述
(ii)遇到7,与 n o d e s [ 0 ] nodes[0] nodes[0]指向的23比较,7应该成为父亲结点,成为一个度为1的结点;于是继续与 n o d e s [ 1 ] nodes[1] nodes[1]指向的17进行比较,7成为父亲结点;最后与 n o d e s [ 2 ] nodes[2] nodes[2]指向的24比较,7仍为父亲结点。这一系列操作后的斐波那契堆如图:
在这里插入图片描述

(iii)遇到18、53,直接让 n o d e s [ 1 ] , n o d e s [ 0 ] nodes[1],nodes[0] nodes[1],nodes[0]指向二者。
在这里插入图片描述

(iv)遇到38,和 n o d e s [ 1 ] nodes[1] nodes[1]指向的18进行比较,18成为父结点被 n o d e s [ 2 ] nodes[2] nodes[2]指向。
在这里插入图片描述
(v)最后进行整理(Consolidate()的do-while结束后),确定新的min指针。
在这里插入图片描述

2.4关键字减值

关键字减值给定一个结点指针,并把它的值减小,重新调整堆的结构。

void DecreaseKey(node *x, T key)
{
    
    
	if(key >= x->data) return;//非减小
	x->data = key;
	
	node *y = x->parent;
	if(y != nullptr && x->data < y->data)
	{
    
    
		Cut(x,y);
		CascadingCut(y);
	}
	if(x->data < min->data)
		min = x;
}

若减值结点尚未破坏堆结构(即仍大于等于其父结点键值),则无需进行调整;否则,堆通过Cut和CascadingCut(级联切断)调整。首先,对调整节点进行切断:把它从其父亲上切断,加入第一层结点中(对应Cut函数)。

void Cut(node *x, node *y)//x为子结点,y为父结点
{
    
    
	if(y->degree == 1)
		y->child = nullptr;
	else
	{
    
    
		x->left->right = x->right;
		x->right->left = x->left;
	}
	y->degree--;
	LeftInsert(head, x);
	x->parent = nullptr;
	x->mark = false;//刚成为独立的结点,标记也要清空
}

之后,对父结点 y y y尝试进行级联切断,它是一种使堆保持较为平衡的操作:

void CascadingCut(node *y)
{
    
    
	node *z = y->parent;
	if(z != nullptr)
	{
    
    
		if(y->mark == false)
			y->mark = true;
		else
		{
    
    
			Cut(y,z);
			CascadingCut(z);
		}
	}
}

y y y有父亲结点 z z z,当 y y y标记为 f a l s e false false时,将其标为 t r u e true true;否则,将 y y y z z z上切割下来,同时检查 z z z是否需要执行该操作。(因为 z z z刚失去一个孩子 y y y,若这是它失去的第二个孩子,它也要被切割下来)

如下图,现在依次将堆中的46键值减少为15、将35减小为5:
(i)原堆:
在这里插入图片描述

(ii)将46切割下来改为15,由于24失去了某个孩子,它被标记,第一条语句执行完毕:
在这里插入图片描述

(iii)将35减小为5,把它从26上切断下来,加入第一层链表:
在这里插入图片描述

(iv)对26执行级联切断,由于它已被标记,切割并加入第一层链表;接着检查24,它也被标记,也要被切割;检查7,它没有父结点,停止。操作结果即26、24依次被加入第一层链表(标记也要被清空)。
在这里插入图片描述

(v)最后修改min指针,第二条语句执行完成。
在这里插入图片描述

2.5删除关键字

有了减小键值和删除最小结点操作,很容易实现删除关键字操作(前提是有指向它的指针)。MIN_VALUE需要在初始化类时给出,如int型设为INT_MIN等。

void Delete(node *x)
{
    
    
	DecreaseKey(x,MIN_VALUE);
	DeleteMin();
}

3.时间复杂度分析

对各种操作进复杂度分析要用到平摊分析的势能法。

3.1势函数

定义斐波那契堆的势函数
ϕ ( H ) = t ( H ) + 2 m ( H ) \phi(H) =t(H)+2m(H) ϕ(H)=t(H)+2m(H),其中 t ( H ) t(H) t(H)为第一层链表中结点个数, m ( H ) m(H) m(H)为标记结点个数。初始条件下对于空堆显然有 ϕ ( H 0 ) = 0 , \phi(H_0)=0, ϕ(H0)=0,对任意状态下有 ϕ ( H ) > ϕ ( H 0 ) . \phi(H)>\phi(H_0). ϕ(H)>ϕ(H0).

3.2插入操作

c i = α i + ϕ ( H i ) − ϕ ( H i − 1 ) c_i=\alpha_i+\phi(H_i)-\phi(H_{i-1}) ci=αi+ϕ(Hi)ϕ(Hi1),其中 α i \alpha_i αi为实际代价,后面的项为势能变化量。 α i \alpha_i αi在插入部分已经说明为 O ( 1 ) O(1) O(1),而 ϕ ( H i ) − ϕ ( H i − 1 ) = [ t ( H i − 1 ) + 1 + 2 m ( H i − 1 ) ] − [ t ( H i − 1 ) + 2 m ( H i − 1 ) ] = 1 \phi(H_i)-\phi(H_{i-1})=[t(H_{i-1})+1+2m(H_{i-1})]-[t(H_{i-1})+2m(H_{i-1})]=1 ϕ(Hi)ϕ(Hi1)=[t(Hi1)+1+2m(Hi1)][t(Hi1)+2m(Hi1)]=1,故均摊代价 c i = O ( 1 ) + 1 = O ( 1 ) . c_i=O(1)+1=O(1). ci=O(1)+1=O(1).

3.3最小结点

显然为 O ( 1 ) O(1) O(1).

3.4删除最小结点

首先计算实际代价:
对于DeleteMin函数的前半部分(不包括Consolidate),有一个循环开销,大小为 O ( D ( n ) ) O(D(n)) O(D(n))(这里尚未给出 D ( n ) D(n) D(n)的界);
再考虑Consolidate中do-while循环的开销:在把删除结点的孩子放入第一层链表后, t ( H ′ ) t(H') t(H)最大为 t ( H ) + D ( n ) − 1 t(H)+D(n)-1 t(H)+D(n)1(删去一个,至多引入 D ( n ) D(n) D(n)个)。在每次do-while循环中,总有一个第一层结点被连接到另一个上,因此至多循环 O ( D ( n ) + t ( H ) + 1 ) = O ( D ( n ) + t ( H ) ) O(D(n)+t(H)+1)=O(D(n)+t(H)) O(D(n)+t(H)+1)=O(D(n)+t(H))次。这两部分加到一起,实际开销为 O ( D ( n ) + t ( H ) ) O(D(n)+t(H)) O(D(n)+t(H)).
再计算势能的变化:删除后,势能增大最大的情况是没有标记结点被删除,且 t ( H ) t(H) t(H)最大变为 D ( n ) + 1 D(n)+1 D(n)+1.因此
Δ ϕ ≤ [ D ( n ) + 1 + 2 m ( H ) ] − [ t ( H ) + 2 m ( H ) ] = D ( n ) + 1 − t ( H ) \Delta\phi\le[D(n)+1+2m(H)]-[t(H)+2m(H)]=D(n)+1-t(H) Δϕ[D(n)+1+2m(H)][t(H)+2m(H)]=D(n)+1t(H);
c i = α i + Δ ϕ ≤ O ( D ( n ) + t ( H ) ) + ( D ( n ) + 1 − t ( H ) ) = O ( D ( n ) ) . c_i=\alpha_i+\Delta\phi\le O(D(n)+t(H))+(D(n)+1-t(H))=O(D(n)). ci=αi+ΔϕO(D(n)+t(H))+(D(n)+1t(H))=O(D(n)).
故均摊复杂度为 O ( D ( n ) ) O(D(n)) O(D(n)).

3.5关键字减值

先计算实际代价:只需计算级联切断的消耗(显然,减值和切断都是 O ( 1 ) O(1) O(1)的)。设级联切断共调用 c c c次,那么实际代价为 O ( c ) . O(c). O(c).
再计算势能变化量:
先考虑第一层结点个数:首先调用第一次Cut操作,为第一层链表增加了一个结点;之后 c c c次级联切断中的 c − 1 c-1 c1次(除了最后一次)都会为第一层链表增加一个结点,故 t ( H ′ ) = t ( H ) + c t(H')=t(H)+c t(H)=t(H)+c
再考虑使标记最多的情况:在 c − 1 c-1 c1次前面的级联切断操作中每次固定会清除一个已有的标记,使得标记数减少 c − 1 c-1 c1;最后一次级联切断又有可能增加一个标记,故
m ( H ′ ) ≤ m ( H ) − ( c − 1 ) + 1 = m ( H ) − c + 2. m(H')\le m(H)-(c-1)+1=m(H)-c+2. m(H)m(H)(c1)+1=m(H)c+2.
因此,均摊复杂度
c i ≤ O ( c ) + [ t ( H ) + c + 2 ( m ( H ) − c + 2 ) ] − [ t ( H ) + 2 m ( H ) ] = O ( c ) + ( 4 − c ) = O ( 1 ) . c_i\le O(c)+[t(H)+c+2(m(H)-c+2)]-[t(H)+2m(H)]=O(c)+(4-c)=O(1). ciO(c)+[t(H)+c+2(m(H)c+2)][t(H)+2m(H)]=O(c)+(4c)=O(1).

3.6删除关键字

由于该操作由前面两个操作叠加而成,易知其平摊复杂度 O ( D ( n ) ) . O(D(n)). O(D(n)).

4.最大度数的界

现在还有一个遗留问题: D n Dn Dn的界。下面几条引理给出其证明:

引理1:设 y i y_i yi x x x的第 i i i个被链入的孩子, x x x共有 k k k个孩子( x . d e g r e e = k x.degree=k x.degree=k),那么有 y 1 . d e g r e e ≥ 0 , y i . d e g r e e ≥ i − 2. y_1.degree\ge 0,y_i.degree\ge i-2. y1.degree0,yi.degreei2.

<证明>显然有 y 1 . d e g r e e ≥ 0. y_1.degree\ge0. y1.degree0. y i y_i yi链入 x x x之前, x . d e g r e e = i − 1 , x.degree=i-1, x.degree=i1,由于只有在Consolidate操作时, y i . d e g r e e = x . d e g r e e y_i.degree=x.degree yi.degree=x.degree y i y_i yi才会被链接到 x x x,因此此时 y i . d e g r e e = i − 1. y_i.degree=i-1. yi.degree=i1.在这之后 y y y至多失去一个孩子,否则它会被级联切断剪掉。故 y i . d e g r e e ≥ i − 2. y_i.degree\ge i-2. yi.degreei2.

引理2:设斐波那契数列为 F 0 = 0 , F 1 = 1 , F k = F k − 1 + F k − 2 ( k ≥ 2 ) , F_0=0,F_1=1,F_k=F_{k-1}+F_{k-2}(k\ge2), F0=0,F1=1,Fk=Fk1+Fk2(k2),那么有 F k + 2 = 1 + ∑ i = 0 k F i . F_{k+2}=1+\sum_{i=0}^{k}F_i. Fk+2=1+i=0kFi.

<证明>采用数学归纳法。
( 1 ) (1) (1) k = 0 k=0 k=0时,等式成立;
( 2 ) (2) (2)假设当 k = k 0 k=k_0 k=k0时,等式成立,往证 k = k 0 + 1 k=k_0+1 k=k0+1时,等式成立。
即有

F k 0 + 2 = 1 + ∑ i = 0 k 0 F i F_{k_0+2}=1+\sum_{i=0}^{k_0}F_i Fk0+2=1+i=0k0Fi

等式两边同时加 F k 0 + 1 , F_{k_0+1}, Fk0+1,

F k 0 + 3 = 1 + F k 0 + 1 + ∑ i = 0 k 0 F i = 1 + ∑ i = 0 k 0 + 1 F i F_{k_0+3}=1+F_{k_0+1}+\sum_{i=0}^{k_0}F_i=1+\sum_{i=0}^{k_0+1}F_i Fk0+3=1+Fk0+1+i=0k0Fi=1+i=0k0+1Fi,得证。

引理3 ∀ k ∈ N , F k + 2 ≥ ϕ k , \forall k\in N,F_{k+2}\ge \phi^k, kN,Fk+2ϕk,其中 ϕ \phi ϕ为黄金分割比 ( 1 + 5 ) / 2. (1+\sqrt 5)/2. (1+5 )/2.

<证明>施归纳于 k k k:
( 1 ) (1) (1) k = 0 , 1 k=0,1 k=0,1时,等式显然成立;
( 2 ) (2) (2)假设当 k ≤ k 0 k \le k_0 kk0时,有 , F k 0 + 2 ≥ ϕ k 0 . ,F_{k_0+2}\ge \phi^{k_0}. ,Fk0+2ϕk0.
那么 F k 0 + 3 = F k 0 + 2 + F k 0 + 1 ≥ ϕ k 0 + ϕ k 0 − 1 = ϕ k 0 − 1 ( 1 + ϕ ) = ϕ k 0 − 1 ϕ 2 = F k 0 + 3 ≥ ϕ k 0 + 1 , F_{k_0+3} = F_{k_0+2}+F_{k_0+1}\ge \phi^{k_0}+\phi^{k_0-1} =\phi^{k_0-1}(1+\phi)=\phi_{k_0-1}\phi^2=F_{k_0+3}\ge \phi^{k_0+1}, Fk0+3=Fk0+2+Fk0+1ϕk0+ϕk01=ϕk01(1+ϕ)=ϕk01ϕ2=Fk0+3ϕk0+1,得证。
( 1 + ϕ = 1 + ( 1 + 5 ) / 2 = ( 3 + 5 ) / 2 = 1+\phi=1+(1+\sqrt5)/2=(3+\sqrt5)/2= 1+ϕ=1+(1+5 )/2=(3+5 )/2=
ϕ 2 = ( 6 + 2 5 ) / 4 = ( 3 + 5 ) / 2 , \phi^2=(6+2\sqrt5)/4=(3+\sqrt5)/2, ϕ2=(6+25 )/4=(3+5 )/2,所以 ϕ 2 = 1 + ϕ . \phi^2=1+\phi. ϕ2=1+ϕ.)

引理4:对于任意度为 k k k的斐波那契堆结点 x x x,有 s i z e ( x ) ≥ F k + 2 ≥ ϕ k , size(x)\ge F_{k+2} \ge \phi^k, size(x)Fk+2ϕk,其中 s i z e ( x ) size(x) size(x)为以 x x x为根的子树结点总个数。

<证明>设 s k s_k sk表示斐波那契堆中度数为 k k k的最小可能 s i z e size size值,那么显然有
( 1 ) s 0 = 1 , s 1 = 2 ; (1)s_0=1,s_1=2; (1)s0=1,s1=2;
( 2 ) s k (2)s_k (2)sk是一个单调递增数列;
( 3 ) (3) (3)任意结点 x x x, s i z e ( x ) ≥ s k . size(x)\ge s_k. size(x)sk.
那么有
s i z e ( x ) ≥ s k size(x)\ge s_k size(x)sk
≥ 2 + ∑ i = 2 k s y i . d e g r e e \ge2+\sum_{i=2}^{k}s_{y_i.degree} 2+i=2ksyi.degree( 2 2 2指的是 x x x自身和它的第一个儿子)
≥ 2 + ∑ i = 2 k s i − 2 \ge2+\sum_{i=2}^{k}s_{i-2} 2+i=2ksi2(由引理1和 s k s_k sk的单调性)
于是有 s k ≥ 2 + ∑ i = 2 k s i − 2 . s_k\ge2+\sum_{i=2}^{k}s_{i-2}. sk2+i=2ksi2.
现证明 s k ≥ F k + 2 : s_k\ge F_{k+2}: skFk+2:
施归纳于 k : k: k:
( 1 ) (1) (1) k = 0 , 1 k=0,1 k=0,1时显然成立。
( 2 ) (2) (2)假设当 k ≤ k 0 k\le k_0 kk0时不等式成立,那么有 s i ≥ F i + 2 . ( i ≤ k 0 ) s_{i}\ge F_{i+2}.(i\le k_0) siFi+2.(ik0)
s k ≥ 2 + ∑ i = 2 k s i − 2 ≥ 2 + ∑ i = 2 k F i = 1 + ∑ i = 0 k F i s_k\ge2+\sum_{i=2}^{k}s_{i-2}\ge2+\sum_{i=2}^{k}F_i=1+\sum_{i=0}^{k}F_i sk2+i=2ksi22+i=2kFi=1+i=0kFi
= F k + 2 =F_{k+2} =Fk+2(引理2)
≥ ϕ k \ge \phi^k ϕk(引理3)
因此有 s i z e ( x ) ≥ s k ≥ F k + 2 ≥ ϕ k size(x) \ge s_k \ge F_{k+2} \ge \phi^k size(x)skFk+2ϕk

引理5:一个 n n n个结点的斐波那契堆中任意结点的最大度数 D n Dn Dn O ( l o g n ) . O(logn). O(logn).

<证明>设 x x x为斐波那契堆中任意结点,设 k = x . d e g r e e . k=x.degree. k=x.degree. n ≥ s i z e ( x ) ≥ ϕ k . n\ge size(x) \ge \phi^k. nsize(x)ϕk.因此 k ≤ l o g ϕ n . k\le log_\phi n. klogϕn.因此 D ( n ) = m a x { k } ≤ l o g ϕ n . D(n)=max\{k\}\le log_\phi n. D(n)=max{ k}logϕn.

至此,证明了删除操作的时间复杂度是 O ( D ( n ) ) = O ( l o g n ) . O(D(n))=O(logn). O(D(n))=O(logn).但在Consolidate过程中,我们用到了更为确切的结论来确定所开数组的大小: D ( n ) ≤ ⌊ l o g n ⌋ . D(n)\le \lfloor logn \rfloor. D(n)logn.下面证明这一结论。(该结论在原书中作为无答案习题给出,如证明有错误还请指出)

定理 D ( n ) ≤ ⌊ l o g n ⌋ . ( D(n)\le \lfloor logn \rfloor.( D(n)logn.( l o g log log 2 2 2为底 ) ) )

<证明>现在证明 s k > 2 k − 1 . s_k>2^{k-1}. sk>2k1.施归纳于 k k k
( 1 ) (1) (1) k = 0 , 1 k=0,1 k=0,1时,不等式显然成立。
( 2 ) (2) (2)假设不等式在 k k k时成立,即 s k > 2 k − 1 , s_k>2^{k-1}, sk>2k1,对于 k + 1 k+1 k+1个度的结点,它显然至少需要两个大小为 s k s_k sk的结点来构成,因此 s k + 1 ≥ 2 s k > 2 k , s_{k+1}\ge 2s_k >2^k, sk+12sk>2k,结论成立。
那么对于任意结点 x , n ≥ s i z e ( x ) ≥ s k > 2 k , k < l o g n . x,n\ge size(x) \ge s_k>2^k,k<logn. x,nsize(x)sk>2k,k<logn.由于 k k k是整数, k ≤ ⌊ l o g n ⌋ . k\le \lfloor logn \rfloor. klogn.因此 D ( n ) = m a x { k } ≤ ⌊ l o g n ⌋ . D(n)=max\{k\}\le \lfloor logn \rfloor. D(n)=max{ k}logn.

4.完整代码

template<typename T>
class FibHeap
{
    
    
	private:
	struct node
	{
    
    
		T data;
		bool mark;
		int degree;
		node *left,*right;
		node *child,*parent;
		node()
		{
    
    
			mark = false;
			degree = 0;
			child = parent = nullptr;
			left = right = this;
		}
	};
	int size;
	node *min;
	node *head;
	T MIN_VALUE;
	void LeftInsert(node *base, node *tmp)
	{
    
    
		tmp->left = base->left;
		tmp->right = base;
		base->left->right = tmp;
		base->left = tmp;
	}
	void link(node *y,node *x)
	{
    
    
		y->left->right = y->right;
		y->right->left = y->left;
		y->parent = x;
		if(x->child == nullptr)
		{
    
    
			x->child = y;
			y->left = y->right = y;
		}
		else LeftInsert(x->child, y);
		x->degree++;
		y->mark = false;
	}
	void Consolidate()
	{
    
     
		int Dn = (int)log2(size)+1;
		node **nodes = new node*[Dn];
		for(int i = 0; i < Dn; i++)
			nodes[i] = nullptr;
		node *ptr = head->right,*nxt;
		do
		{
    
    
			int d;
			node *x,*y;
			x = ptr;
			if(x == head) continue; 
			d = x->degree;
			nxt = ptr->right;
			while(nodes[d] != nullptr)
			{
    
    
				y = nodes[d];
				if(x->data > y->data)
					std::swap(x, y);
				link(y,x);
				nodes[d] = nullptr;
				d++;
			}
			nodes[d] = x;
			ptr = nxt;
		}while(ptr != head);
		min = nullptr;
		head->left = head->right = head;
		for(int i = 0; i < Dn; i++)
		{
    
    
			if(nodes[i] != nullptr)
			{
    
    
				if(min == nullptr)
				{
    
    
					min = nodes[i];
					LeftInsert(head,min);
				}
				else
				{
    
    
					LeftInsert(head, nodes[i]);
					if(nodes[i]->data < min->data)
						min = nodes[i];
				}
			}
		}
	}
	public:
	FibHeap()
	{
    
    
		size = 0;
		min = nullptr;
		head = new node;
	}
	FibHeap(T MIN_VALUE)
	{
    
    
		size = 0;
		min = nullptr;
		head = new node;
		this->MIN_VALUE = MIN_VALUE;
	}
	T GetMin()
	{
    
    
		return min->data;
	}
	int Size()
	{
    
    
		return size;
	}
	void Insert(T data)
	{
    
    
		node *tmp = new node;
		tmp->degree = 0;
		tmp->data = data;
		tmp->parent = tmp->child = nullptr;
		tmp->left = tmp->right = tmp;
		tmp->mark = false;
		if(min == nullptr)
		{
    
    
			min = tmp;
			LeftInsert(head, min);
		}
		else
		{
    
    
			LeftInsert(min, tmp);
			if(data < min->data)
				min = tmp;
		}
		size++;
	}
	FibHeap<T> Merge(FibHeap<T> H2)//TOCHECK
	{
    
    
		node *ptr = head->right;
		do
		{
    
    
			H2.Insert(ptr->data);
			ptr = ptr->right;
		}while(ptr != head);
		if(H2.min == nullptr || (min != nullptr && min->data < H2.GetMin()))
			H2.min = min;
		return H2;
	}
	T DeleteMin()
	{
    
    
		T ret = min->data;
		node *ptr = min;
		if(ptr != nullptr)
		{
    
    
			node *tmp = min->child,*nxt;
			for(int i = 0; i < min->degree; i++)
			{
    
    
				nxt = tmp->right;
				LeftInsert(head, tmp);
				tmp = nxt;
			}
			ptr->left->right = ptr->right;
			ptr->right->left = ptr->left;

			if(ptr == ptr->right)
				min = nullptr;
			else
			{
    
    
				min = min->right;
				if(min == head) min = min->right;
				Consolidate();
			}
			size--;
		}
		return ret;
	}
	void Cut(node *x, node *y)
	{
    
    
		if(y->degree == 1)
			y->child = nullptr;
		else
		{
    
    
			x->left->right = x->right;
			x->right->left = x->left;
		}
		y->degree--;
		LeftInsert(head, x);
		x->parent = nullptr;
		x->mark = false;
	}
	void CascadingCut(node *y)
	{
    
    
		node *z = y->parent;
		if(z != nullptr)
		{
    
    
			if(y->mark == false)
				y->mark = true;
			else
			{
    
    
				Cut(y,z);
				CascadingCut(z);
			}
		}
	}
	void DecreaseKey(node *x, T key)
	{
    
    
		if(key >= x->data) return;
		x->data = key;
		
		node *y = x->parent;
		if(y != nullptr && x->data < y->data)
		{
    
    
			Cut(x,y);
			CascadingCut(y);
		}
		if(x->data < min->data)
			min = x;
	}
	void Delete(node *x)
	{
    
    
		DecreaseKey(x,MIN_VALUE);
		DeleteMin();
	}
};

猜你喜欢

转载自blog.csdn.net/wyn1564464568/article/details/122916441
今日推荐