【数据结构】【OI】二项堆的原理及代码实现

来源:互联网 发布:xp系统链接网络打印机 编辑:程序博客网 时间:2024/06/16 05:12

二项堆

二项堆(Binomial Heap)是二项树(Binomial Tree)的集合,它与二叉堆、左偏树、斜堆一样,也是用来实现优先队列操作的。二项堆支持查找最小值、删除最小值、插入、删除、合并操作,每项操作时间复杂度为O(logn)(经过优化后,查找也是O(1))。为什么要学习二项堆?首先二项堆的合并操作为O(logn)的,而且它拥有时间复杂度,可以进行可持久化操作。

二项树

二项树是一组多叉树的序列,二项树满足以下性质:

  • k棵树一共有2k个节点,第0棵树有一个节点
  • k棵树的第i(0,k1)行有Cik个节点(节点按二项分布)
  • k棵树由前k1棵树组成
  • 任意高度只有一颗树,二项树的高度称为它的度数

由以上结论,任意大小的二项堆都可以用唯一的二项树集合表示,如一个有21个节点的二项堆,可以被一个高度为5的树(节点数为16)、高度为3的树(节点数为4)、高度为1的树表示,即使用类似二进制的方式表示。

二项堆的实现

1、构造

对于每个二项堆,定义根节点为第一棵根节点的根,使用链表将每棵二项树的根连接起来(这个链表叫根链表),就得到一个完整的二项堆结构。

对于每个节点,存有其子树的度数、自身的值、Next、Fa、Son

struct Node{    int Fa,Son,Next,d,v;}T[10000];

2、查询

由于每颗二项树都满足堆的性质,所以我们用链表结构将二项树连接起来,取每棵树的最小值即可。

3、合并

合并操作是二项堆的重要也是基本操作,二项堆的插入和删除操作也在它的基础上进行,合并操作一共有两个步骤:

(1)将两个二项堆的根链表按度数从小到大顺序合在一起,得到一个未成形的二项堆
(2)将新堆中度数相同的树合并在一起,形成度数+1的新二项树(从小到大进行)

代码实现如下:

int Merge(int h1,int h2){    if(!h1)return h2;    if(!h2)return h1;//防止prev访问越界    int _prev=0,heap;    while(h1&&h2){        if(T[h1].d<=T[h2].d){//依次选取度数小的连接到新堆             if(_prev==0)_prev=h1,heap=h1;            else T[_prev].Next=h1,_prev=h1;            h1=T[h1].Next;        }        else {            if(_prev==0)_prev=h2,heap=h2;            else T[_prev].Next=h2,_prev=h2;            h2=T[h2].Next;        }    }    if(h1)T[_prev].Next=h1;    else T[_prev].Next=h2;    return heap;}void Link(int son,int fa){    T[son].Fa=fa;    T[son].Next=T[fa].Son;//Son记录左起第一个儿子,兄弟之间形成一个链表     T[fa].Son=son;    T[fa].d++;}int Union(int h1,int h2){    int heap=Merge(h1,h2);    int x=heap,_prev=0,_next=T[x].Next;    while(_next!=0){        if(T[x].d!=T[_next].d)            _prev=x,x=_next;//度数不相同,不需要合并         else if(T[x].v<=T[_next].v){            T[x].Next=T[_next].Next;            Link(_next,x);//度数相同,将较大的树连接到小树上         }        else {            if(_prev==0)heap=_next;            else T[_prev].Next=_next;            Link(x,_next);            x=_next;        }        _next=T[x].Next;    }    return heap;}

4、插入

将插入的节点看作一个堆与原堆进行合并即可

5、删除最小值

在根链表中找到这个节点,将其在链表中删除,并将其子节点链表反序,作为一个新堆与原堆合并

int Delete(int h){    int heap=h,Minn=GetMin(h),_prev=0,_next;    while(T[h].v!=Minn)_prev=h,h=T[h].Next;    if(_prev)T[_prev].Next=T[h].Next;    else heap=T[h].Next;//链表的删除     T[h].Next=0,h=T[h].Son;    T[h].Fa=0;    _prev=0;    while(T[h].Next){        _next=T[h].Next;        T[h].Next=_prev;        _prev=h,h=_next;    }//节点链表反序     T[h].Next=_prev;    heap=Union(h,heap);//重新合并     return heap;}

6、可持久化

等我研究出了再更新吧=_=

原创粉丝点击