本文改编自《算法导论》第三版第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 Dn≤⌊logn⌋的结论。注意数组大小应为 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)−ϕ(Hi−1),其中 α 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)−ϕ(Hi−1)=[t(Hi−1)+1+2m(Hi−1)]−[t(Hi−1)+2m(Hi−1)]=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)+1−t(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)+1−t(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 c−1次(除了最后一次)都会为第一层链表增加一个结点,故 t ( H ′ ) = t ( H ) + c t(H')=t(H)+c t(H′)=t(H)+c;
再考虑使标记最多的情况:在 c − 1 c-1 c−1次前面的级联切断操作中每次固定会清除一个已有的标记,使得标记数减少 c − 1 c-1 c−1;最后一次级联切断又有可能增加一个标记,故
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)−(c−1)+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). ci≤O(c)+[t(H)+c+2(m(H)−c+2)]−[t(H)+2m(H)]=O(c)+(4−c)=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.degree≥0,yi.degree≥i−2.
<证明>显然有 y 1 . d e g r e e ≥ 0. y_1.degree\ge0. y1.degree≥0.在 y i y_i yi链入 x x x之前, x . d e g r e e = i − 1 , x.degree=i-1, x.degree=i−1,由于只有在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=i−1.在这之后 y y y至多失去一个孩子,否则它会被级联切断剪掉。故 y i . d e g r e e ≥ i − 2. y_i.degree\ge i-2. yi.degree≥i−2.
引理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=Fk−1+Fk−2(k≥2),那么有 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, ∀k∈N,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 k≤k0时,有 , 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+ϕk0−1=ϕk0−1(1+ϕ)=ϕk0−1ϕ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=2ksi−2(由引理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}. sk≥2+∑i=2ksi−2.
现证明 s k ≥ F k + 2 : s_k\ge F_{k+2}: sk≥Fk+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 k≤k0时不等式成立,那么有 s i ≥ F i + 2 . ( i ≤ k 0 ) s_{i}\ge F_{i+2}.(i\le k_0) si≥Fi+2.(i≤k0)
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 sk≥2+∑i=2ksi−2≥2+∑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)≥sk≥Fk+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. n≥size(x)≥ϕk.因此 k ≤ l o g ϕ n . k\le log_\phi n. k≤logϕ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>2k−1.施归纳于 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>2k−1,对于 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+1≥2sk>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,n≥size(x)≥sk>2k,k<logn.由于 k k k是整数, k ≤ ⌊ l o g n ⌋ . k\le \lfloor logn \rfloor. k≤⌊logn⌋.因此 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();
}
};