斐波那契堆 数据结构说解

斐波那契堆的定义

根据《算法导论》的定义,一个斐波那契堆是一系列具有最小堆序的有根树的集合。也就是说,每棵树均遵循最小堆性质:每个节点的关键字大于或等于它的父节点的关键字。

对于斐波那契堆中的一个节点,具有如下属性:
parent 指向该节点的父节点;
left 和 right 指向该节点的兄弟节点,并将同一个父节点下的所有子节点连成一个双向循环链表;
child 指向该节点的一个子节点;
key 记录该节点的关键字;
degree 记录该节点的子节点数目,即该节点的度数;
mark 记录该节点是否被切除过子节点,没有失去过子节点时 mark 为 false,切去一个子节点时 mark 为 true,不存在切去多个子节点的节点。

任一斐波那契堆只记录指向其最小关键字的指针,记为 min。根据定义,min 一定指向处于该斐波那契堆根链表中的节点。

对于斐波那契堆的定义代码如下:

#define NIL -1
int Array[MAX_N],FH_MemTop=0;
struct FIBONACCI_HEAP_NODE
{
    bool mark;
    int child,degree,key,left,parent,right;
}FHData[MAX_N];
struct FIBONACCI_HEAP
{
    int min;
}FibHeap[MAX_H];

斐波那契堆的单元素插入维护

插入时,直接将新节点并入待插入的斐波那契堆的根链表中即可。

对于斐波那契堆的单元素插入维护代码如下:

#define FH_AddNode(now,sbl,fah) \
{ \
    if(sbl!=NIL) \
        FHData[now].left=FHData[sbl].left, \
        FHData[now].right=sbl, \
        FHData[FHData[sbl].left].right=now, \
        FHData[sbl].left=now; \
    else \
        FHData[now].left=FHData[now].right=sbl=now; \
    FHData[now].parent=fah; \
}
void FH_Insert(FIBONACCI_HEAP &fh0,int num)
{
    int posi=FH_MemTop++;
    FHData[posi].child=FHData[posi].parent=NIL;
    FHData[posi].degree=0;
    FHData[posi].key=num;
    FHData[posi].mark=false;
    FH_AddNode(posi,fh0.min,NIL);
    if(FHData[fh0.min].key>num)
        fh0.min=posi;
}

斐波那契堆的合并维护

合并时,直接将两个斐波那契堆的根链表合并即可。由于根链表用双向循环链表表示,所以该操作只需要花费常数时间。

对于斐波那契堆的合并维护代码如下:

FIBONACCI_HEAP FH_Union(FIBONACCI_HEAP &fh1,FIBONACCI_HEAP &fh2)
{
    FIBONACCI_HEAP fh0;
    int temp;
    if(fh1.min!=NIL)
    {
        fh0.min=fh1.min;
        if(fh2.min!=NIL)
            FHData[FHData[fh0.min].left].right=fh2.min,
            FHData[FHData[fh2.min].left].right=fh0.min,
            temp=FHData[fh0.min].left,
            FHData[fh0.min].left=FHData[fh2.min].left,
            FHData[fh2.min].left=temp;
    }
    else
        fh0.min=fh2.min;
    fh1.min=fh2.min=NIL;
    return fh0;
}

斐波那契堆的单最值查询删除维护

在对斐波那契堆进行最小值删除操作时,我们将 min 指向的节点的所有子节点移至根链表中,并将 min 指向的节点从根链表中删去。在这之后,我们将该斐波那契堆的所有堆序树进行整理,使得整理后的斐波那契堆中根链表中所有节点的 degree 值各不相同。

该过程的具体做法是,构建一个数组,下标为 i 的位置记录 degree 值为 i 的堆序树的根下标。依次搜索该斐波那契堆根链表的所有节点,并移入数组中。当发生冲突时,使关键字相对大的节点成为关键字相对小的节点的子节点,并使关键字相对小的节点的 degree 值加一的方式,合并两棵堆序树。最后,将数组中指向的所有堆序树的根节点重新移入原斐波那契堆以完成堆序树的合并。在此过程中求出新的最小关键字的节点,更新 min 指针即可。

对于斐波那契堆的单最值查询删除维护代码如下:

#define FH_RemoveNode(now,sbl) \
{ \
    FHData[FHData[now].left].right=FHData[now].right; \
    FHData[FHData[now].right].left=FHData[now].left; \
    if(now==sbl) \
    { \
        sbl=FHData[now].left; \
        if(now==sbl) \
            sbl=NIL; \
    } \
    FHData[now].left=FHData[now].right=now; \
    FHData[now].parent=NIL; \
}
int FH_Link(FIBONACCI_HEAP &fh0,int fh1,int fh2)
{
    if(FHData[fh1].key>FHData[fh2].key)
        return FH_Link(fh0,fh2,fh1);
    FH_RemoveNode(fh2,fh0.min);
    FH_AddNode(fh2,FHData[fh1].child,fh1);
    FHData[fh1].degree++;
    FHData[fh2].mark=false;
    return fh1;
}
void FH_Consolidate(FIBONACCI_HEAP &fh0)
{
    int d,m=0,now;
    memset(Array,NIL,sizeof(Array));
    while(fh0.min!=NIL)
    {
        now=fh0.min;
        FH_RemoveNode(now,fh0.min);
        for(d=FHData[now].degree;Array[d]!=NIL;Array[d++]=NIL)
            now=FH_Link(fh0,Array[d],now);
        Array[d]=now;
        m=d>m?d:m;
    }
    for(int i=0;i<=m;i++)
        if(Array[i]!=NIL)
        {
            FH_AddNode(Array[i],fh0.min,NIL);
            if(FHData[fh0.min].key>FHData[Array[i]].key)
                fh0.min=Array[i];
        }
}
int FH_ExtractMin(FIBONACCI_HEAP &fh0)
{
    int posi=NIL;
    if(fh0.min!=NIL)
    {
        while(FHData[fh0.min].child!=NIL)
        {
            posi=FHData[fh0.min].child;
            FHData[fh0.min].child=FHData[FHData[fh0.min].child].right;
            FH_RemoveNode(posi,FHData[fh0.min].child);
            FH_AddNode(posi,fh0.min,NIL);
        }
        FHData[posi=fh0.min].degree=0;
        FH_RemoveNode(posi,fh0.min);
        if(fh0.min!=NIL)
            FH_Consolidate(fh0);
    }
    return posi;
}

斐波那契堆的单元素关键字减值维护

该过程将给定位置的元素的关键字减值并维护斐波那契堆的性质。具体操作时判断该节点的关键字与其父节点的关键字之间的关系,堆序被破坏时将该节点从其父节点的子链表中删去并移入根链表中,并清除标记。当父节点已被标记,即父节点已被切除过一个子节点时,引发级联切断,将父节点从祖父节点的子链表中删去并移入根链表中,并清除标记,继续向上判断,直至到达根链表中的节点或未标记的节点,标记之。

对于斐波那契堆的单元素关键字减值维护代码如下:

void FH_Cut(FIBONACCI_HEAP &fh0,int pos)
{
    FH_RemoveNode(pos,FHData[FHData[pos].parent].child);
    FH_AddNode(pos,fh0.min,NIL);
    FHData[FHData[pos].parent].degree--;
    FHData[pos].mark=false;
}
void FH_CascadingCut(FIBONACCI_HEAP &fh0,int pos)
{
    FHData[pos].degree--;
    if(FHData[pos].parent!=NIL)
        if(FHData[pos].mark)
            FH_CascadingCut(fh0,FHData[pos].parent),
            FH_Cut(fh0,pos);
        else
            FHData[pos].mark=true;
}
void FH_DecreaseKey(FIBONACCI_HEAP &fh0,int pos,int k)
{
    if(FHData[pos].key<=k)
        return;
    FHData[pos].key=k;
    if(FHData[pos].parent!=NIL&&FHData[pos].key<FHData[FHData[pos].parent].key)
        FH_CascadingCut(fh0,FHData[pos].parent),
        FH_Cut(fh0,pos);
    if(FHData[fh0.min].key>k)
        fh0.min=pos;
}

斐波那契堆的单元素删除维护

删除时,将给定位置节点的关键字赋为无穷小,并调用一次单元素关键字减值维护,之后再调用一次最小值删除维护即可。

对于斐波那契堆的单元素删除维护代码如下:

void FH_Delete(FIBONACCI_HEAP &fh0,int pos)
{
    FH_DecreaseKey(fh0,pos,-0x80000000);
    FH_ExtractMin(fh0);
}

斐波那契堆的时间效率分析

对于斐波那契堆的时间效率分析,《算法导论》上有详细的介绍。具体是用势方法分析斐波那契堆操作的性能。对于一个给定的斐波那契堆 H,用 t(H) 表示 H 的根链表中堆序树的数目,用 m(H) 表示 H 中已标记的节点数目,定义斐波那契堆 H 的势函数为:

Φ(H)=t(H)+2m(H)

对于插入和合并,其时间效率显然是 O(1) 的。删除最小值操作中,没有标记变动,设一个 n 个节点的斐波那契堆中任何节点的最大度数的上界为 D(n),则删除最小值操作的时间复杂度为 O(D(n))。用势来解释,即摊还代价为:

  O(D(n) + t(H)) + ((D(n) + 1) + 2m(H)) - (t(H) + 2m(H))
= O(D(n)) + O(t(H)) - t(H)
= O(D(n))

抽取最小节点前的势为 t(H) + 2m(H),操作后势最大为 (D(n) + 1) + 2m(H)。通过增大势的单位来支配隐藏在 O(t(H)) 中的常数,因此抽取最小节点的摊还代价为 O(D(n))。

对于关键字减值,设调用了 c 次级联切断,则其摊还代价为 O(1),解释如下:

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

下面分析 D(n) = O(lgn)。

斐波那契数列递推公式:

Fk=01Fk1+Fk2k=0k=1k2

归纳假设可以证明:
Fk+2=1+i=0kFi

ϕ 为等式 x2=x+1 的正根,即 ϕ=(1+5)/2 ,则归纳假设可以证明:
Fk+2ϕk

设斐波那契堆中任意节点 x,设 k = x.degree,其子节点 y1,y2,...,yk ,并以链入 x 的先后顺序排列,则 yi.degreei1 。由于任意节点最多删去一个子节点,所以 y1.degree0,yi.degreei2

如此一来,则有 size(x)Fk+2ϕk 。证明过程如下:设 sk 表示斐波那契堆中度数为 k 的任意节点的最小可能 size,则:

size(x)sk2+i=2ksyi.degree2+i=2ksi22+i=2kFi=1+i=0kFi=Fk+2ϕk

所以, nsize(x)ϕk ,则 klogϕn 。如此便证明了 D(n) = O(lgn)。

斐波那契堆的总结

由于斐波那契堆编程复杂度较高,故其效率虽高于一般的可合并堆,但其理论价值大于其实用价值。虽然其实用价值偏低,但仍有可取之处。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值