二项堆

二项堆是可合并堆的数据结构,应该功能应该类似左偏树。

二项树

二项树Bk是一种递归定义的有序树,如下图所示。

a)二项树Bk的递归定义,三角形表示有根的子树,b)二项树B0至B4,B4中显示出了各节点的深度,c)以另一种方式来看二项树Bk

二项树的性质:

1)共有2k个结点。

2)树的高度为k。

3)在深度i处恰有Cik个结点。

4)根的度数(子女的个数)为k,它大于任何其他结点的度数;如果根的子女从左到右的编号设为k-1, k-2, …, 0,子女i是子树Bi的根。

二项堆

二项堆H是由一组满足下面的二项堆性质的二项树组成:
1)H中的每个二项树遵循最小堆性质:节点的关键字大于或等于其父节点的关键字。
2)对任意非负数k,在H中至多有一颗二项树的根具有度数k。
第一个性质,我们知道在一棵最小堆有序的二项树中,其根包含了树中最小的关键字。
第二个性质,我们知道在包含n个节点的二项堆H中,包含至多floor(lgn)+1棵二项树。

二项堆的表示
每个节点x包含指向其父亲节点的指针parent,指向其最左孩子的指针leftchild,以及指向x的紧右兄弟的指针sibling。每个节点都包含域degree,表示x的子女个数
如图,在一个二项堆中的个二项树的根被组织成一个链表,称为根表。

二项树的ADT:

  1. struct BinHeapNode 
  2.     int key,degree;//key是键值,degree表示子女个数 
  3.     BinHeapNode *parent,*leftChild,*sibling; 
  4.     //leftChild是节点x的最左孩子指针,sibling是指向x的紧右兄弟的指针 
  5.     //也就是说,每层节点都有指针指向其右边的兄弟节点,如果某个节点是最右边的,那么sibling是null 
  6. }; 
struct BinHeapNode
{
    int key,degree;//key是键值,degree表示子女个数
    BinHeapNode *parent,*leftChild,*sibling;
    //leftChild是节点x的最左孩子指针,sibling是指向x的紧右兄弟的指针
    //也就是说,每层节点都有指针指向其右边的兄弟节点,如果某个节点是最右边的,那么sibling是null
};

对二项堆的操作

1)寻找最小关键字
由于二项树符合最小堆的性质,那么二项堆的最小关键字就在其根表中
伪代码:Binomial-Heap-Minimum(H)
1  y ← NIL
2  x ← head[H]
3  min ← ∞
4  while x ≠ NIL
5     do if key[x] < min
6           then min ← key[x]
7                y ← x
8         x ← sibling[x]
9  return y
  1. //返回最小关键字的指针 
  2. BinHeapNode* BinHeapMin(BinHeapNode *heap) 
  3.     BinHeapNode *y = NULL,*x = heap; 
  4.     int min = 1<<30; 
  5.     //由于符合最小堆的性质,所有最小值就在根节点那一行 
  6.     while(x != NULL) 
  7.     { 
  8.         if(x->key < min) 
  9.         { 
  10.             y = x; 
  11.             min = x->key; 
  12.         } 
  13.         x = x->sibling;//向右边走,到另一个二项树的根 
  14.     } 
  15.     return y; 
//返回最小关键字的指针
BinHeapNode* BinHeapMin(BinHeapNode *heap)
{
    BinHeapNode *y = NULL,*x = heap;
    int min = 1<<30;
    //由于符合最小堆的性质,所有最小值就在根节点那一行
    while(x != NULL)
    {
        if(x->key < min)
        {
            y = x;
            min = x->key;
        }
        x = x->sibling;//向右边走,到另一个二项树的根
    }
    return y;
}

2)合并两个二项堆
Binomial-Heap-Union是反复连接根节点度数相同的各二项树。下面过程是,将以节点y为根的Bk-1树与以节点z为根的Bk-1树连接起来,即:它使z成为y的父节点,并成为一棵Bk树的根。
BINOMIAL-LINK(y, z)
1  p[y] ← z
2  sibling[y] ← child[z]
3  child[z] ← y
4  degree[z] ← degree[z] + 1
合并操作分为两个阶段,第一阶段是执行Binomial-Heap-Merge,将二项堆H1和H2的根表归并排序成一个链表H,按照度数单调递增排序。
第二阶段是将度数相等的根合并,直到每个度数的根至多为一个为止。
合并阶段分为四种情况:当遍历到根表节点x的时候
Case 1:degree[x] != degree[sibling[x]],那么这时只需要将指针向右移动即可
Case 2:当x为具有相同度数的三个根中的第一个的时候,即degree[x] == degree[sibling[x]] == degree[sibling[sibling[x]]],这时候处理的情况同Case 1相同,当下次迭代的时候将执行Case 3或Case 4,把三个相等度数节点的后两个合并起来
Case 3 Case4 都是degree[x] == degree[sibling[x]] != degree[sibling[sibling[x]]],这时候按照关键字的大小进行合并
Case 3:key[x] <= key[sibling[x]]那么就将sibling[x]连接到x上,x作为父节点
Case 4:key[x] > key[sibling[x]]那么就将x连接到sibling[x]上,sibling[x]作为父节点
时间复杂度是O(logn),四种Case如下

一个完整的合并操作示意图


  1. //将以H1为根的树和以H2为根的树连接,使H2变成H1的父节点 
  2. void BinLink(BinHeapNode *&H1,BinHeapNode *&H2) 
  3.     H1->parent = H2; 
  4.     H1->sibling = H2->leftChild;//向右连接H2的最左孩子 
  5.     H2->leftChild = H1;//更新H2的最左孩子 
  6.     H2->degree++;//H2的degree++ 
//将以H1为根的树和以H2为根的树连接,使H2变成H1的父节点
void BinLink(BinHeapNode *&H1,BinHeapNode *&H2)
{
    H1->parent = H2;
    H1->sibling = H2->leftChild;//向右连接H2的最左孩子
    H2->leftChild = H1;//更新H2的最左孩子
    H2->degree++;//H2的degree++
}
BINOMIAL-HEAP-MERGE ,将H1和H2的根表合并成一个按度数的单调递增次序排列的链表
  1. //将二项堆H1和H2的根表合并成一个按度数(degree)单调递增次序的链表,合并到H3然后给heap 
  2. //类似于归并排序过程 
  3. BinHeapNode* BinHeapMerge(BinHeapNode *&H1,BinHeapNode *&H2) 
  4.         BinHeapNode *heap = NULL, *fHeap=NULL,*sHeap=NULL,*pre_H3=NULL,*H3=NULL; 
  5.         fHeap = H1; 
  6.         sHeap = H2; 
  7.         while(fHeap != NULL && sHeap != NULL) 
  8.         { 
  9.             if(fHeap->degree <= sHeap->degree) 
  10.             { 
  11.                 H3 = fHeap; 
  12.                 fHeap=fHeap->sibling;//向右走 
  13.             } 
  14.             else 
  15.             { 
  16.                 H3 = sHeap; 
  17.                 sHeap = sHeap->sibling;//向右走 
  18.             } 
  19.             if(pre_H3 == NULL)//第一次 
  20.             { 
  21.                 pre_H3 = H3;//把首节点给pre_H3 
  22.                 heap = H3;//给heap 
  23.             } 
  24.             else 
  25.             { 
  26.                 pre_H3->sibling = H3;//pre_H3->sibling = H3 
  27.                 pre_H3 = H3; 
  28.             } 
  29.         }//while 
  30.         if(fHeap != NULL) H3->sibling = fHeap; 
  31.         else H3->sibling = sHeap; 
  32.         return heap; 
//将二项堆H1和H2的根表合并成一个按度数(degree)单调递增次序的链表,合并到H3然后给heap
//类似于归并排序过程
BinHeapNode* BinHeapMerge(BinHeapNode *&H1,BinHeapNode *&H2)
{
		BinHeapNode *heap = NULL, *fHeap=NULL,*sHeap=NULL,*pre_H3=NULL,*H3=NULL;
        fHeap = H1;
        sHeap = H2;
        while(fHeap != NULL && sHeap != NULL)
        {
            if(fHeap->degree <= sHeap->degree)
            {
                H3 = fHeap;
                fHeap=fHeap->sibling;//向右走
            }
            else
            {
                H3 = sHeap;
                sHeap = sHeap->sibling;//向右走
            }
            if(pre_H3 == NULL)//第一次
            {
                pre_H3 = H3;//把首节点给pre_H3
                heap = H3;//给heap
            }
            else
            {
                pre_H3->sibling = H3;//pre_H3->sibling = H3
                pre_H3 = H3;
            }
        }//while
        if(fHeap != NULL) H3->sibling = fHeap;
        else H3->sibling = sHeap;
		return heap;
}
Binomil-Heap-Union伪代码:
1  H ← MAKE-BINOMIAL-HEAP()
2  head[H] ← BINOMIAL-HEAP-MERGE(H1, H2)
3  free the objects H1 and H2 but not the lists they point to
4  if head[H] = NIL
5     then return H
6  prev-x ← NIL
7  x ← head[H]
8  next-x ← sibling[x]
9  while next-x ≠ NIL
10      do if (degree[x] ≠ degree[next-x]) or
                (sibling[next-x] ≠ NIL and degree[sibling[next-x]] = degree[x])
11            then prev-x ← x                                ▹ Cases 1 and 2
12                 x ← next-x                                ▹ Cases 1 and 2
13            else if key[x] ≤ key[next-x]
14                    then sibling[x] ← sibling[next-x]          ▹ Case 3
15                         BINOMIAL-LINK(next-x, x)               ▹ Case 3
16                    else if prev-x = NIL                        ▹ Case 4
17                            then head[H] ← next-x              ▹ Case 4
18                            else sibling[prev-x] ← next-x       ▹ Case 4
19                         BINOMIAL-LINK(x, next-x)               ▹ Case 4
20                         x ← next-x                            ▹ Case 4
21         next-x ← sibling[x]
22  return H
  1. BinHeapNode* BinHeapUnion(BinHeapNode *&H1,BinHeapNode *&H2) 
  2.     BinHeapNode *heap = NULL,*pre_x = NULL,*x=NULL,*next_x=NULL; 
  3.     heap = BinHeapMerge(H1,H2); 
  4.     if(heap == NULL) return heap; 
  5.  
  6.     x = heap; 
  7.     next_x = x->sibling; 
  8.     while(next_x != NULL) 
  9.     { 
  10.         //case1: degree[x] != degree[sibling[x]] 
  11.         if((x->degree!=next_x->degree) 
  12.            //case2:degree[x]=degree[next_x]=degree[sibling[next_x]] 
  13.                 ||(next_x->sibling!=NULL && x->degree == next_x->sibling->degree)) 
  14.         {//只需要向右移 
  15.             pre_x = x; 
  16.             x = next_x; 
  17.         } 
  18.         //case3:degree[x]=degree[next_x]!=degree[sibling[next_x]]&x->key <= next_x->key 
  19.         //将sibling[x]连接到x上 
  20.         else if(x->key <= next_x->key) 
  21.         { 
  22.             x->sibling = next_x->sibling; 
  23.             BinLink(next_x,x); 
  24.         } 
  25.         else 
  26.         {//将x连接到sibling[x]上 
  27.             if(pre_x == NULL) heap = next_x; 
  28.             else pre_x->sibling = next_x; 
  29.             BinLink(x,next_x); 
  30.             x = next_x; 
  31.         } 
  32.         next_x = x->sibling; 
  33.     } 
  34.     return heap; 
BinHeapNode* BinHeapUnion(BinHeapNode *&H1,BinHeapNode *&H2)
{
    BinHeapNode *heap = NULL,*pre_x = NULL,*x=NULL,*next_x=NULL;
    heap = BinHeapMerge(H1,H2);
    if(heap == NULL) return heap;

    x = heap;
    next_x = x->sibling;
    while(next_x != NULL)
    {
        //case1: degree[x] != degree[sibling[x]]
        if((x->degree!=next_x->degree)
           //case2:degree[x]=degree[next_x]=degree[sibling[next_x]]
                ||(next_x->sibling!=NULL && x->degree == next_x->sibling->degree))
        {//只需要向右移
            pre_x = x;
            x = next_x;
        }
        //case3:degree[x]=degree[next_x]!=degree[sibling[next_x]]&x->key <= next_x->key
        //将sibling[x]连接到x上
        else if(x->key <= next_x->key)
        {
            x->sibling = next_x->sibling;
            BinLink(next_x,x);
        }
        else
        {//将x连接到sibling[x]上
            if(pre_x == NULL) heap = next_x;
            else pre_x->sibling = next_x;
            BinLink(x,next_x);
            x = next_x;
        }
        next_x = x->sibling;
    }
    return heap;
}
3)插入一个节点
插入节点的操作就是先构造一个只包含一个节点的二项堆H1,再在O(logn)时间内,将其与包含n个节点的二项堆H合并
BINOMIAL-HEAP-INSERT(H, x)
1  H′ ← MAKE-BINOMIAL-HEAP()
2  p[x] ← NIL
3  child[x] ← NIL
4  sibling[x] ← NIL
5  degree[x] ← 0
6  head[H′] ← x
7  H ← BINOMIAL-HEAP-UNION(H, H′)

  1. BinHeapNode* BinHeapInsert(int key,BinHeapNode *heap) 
  2.     BinHeapNode *newHeap = NULL; 
  3.     newHeap = new BinHeapNode; 
  4.     memset(newHeap,0,sizeof(BinHeapNode));//初始化新节点的值,degree,leftChild,sibling等 
  5.     newHeap->key = key; 
  6.     if(heap == NULL) heap = newHeap; 
  7.     else 
  8.     { 
  9.          heap = BinHeapUnion(heap,newHeap); 
  10.          newHeap = NULL; 
  11.          delete newHeap; 
  12.     } 
  13.     return heap; 
BinHeapNode* BinHeapInsert(int key,BinHeapNode *heap)
{
    BinHeapNode *newHeap = NULL;
    newHeap = new BinHeapNode;
    memset(newHeap,0,sizeof(BinHeapNode));//初始化新节点的值,degree,leftChild,sibling等
    newHeap->key = key;
    if(heap == NULL) heap = newHeap;
    else
    {
         heap = BinHeapUnion(heap,newHeap);
         newHeap = NULL;
         delete newHeap;
    }
    return heap;
}
4)抽取具有最小关键字的节点
该过程首先需要找到最小关键字的节点,然后将该节点删除,然后将x的子节点倒序排列,重新组成一个二项堆H1,然后与原来的二项堆H合并组成新的二项堆。


  1. //抽取具有最小关键字的节点 
  2. BinHeapNode* BinHeapExtractMin(BinHeapNode *&heap) 
  3.     BinHeapNode *pre_y=NULL,*y=NULL,*x=heap; 
  4.     int min = 1<<30; 
  5.     while(x != NULL) 
  6.     { 
  7.         if(x->key < min) 
  8.         { 
  9.             min = x->key; 
  10.             pre_y = y; 
  11.             y = x; 
  12.         } 
  13.         x = x->sibling;//向根表走 
  14.     }//找到最小关键字的节点 
  15.     if(y == NULL) return y;//空 
  16.     //删除最小关键字节点 
  17.     if(pre_y == NULL) heap = heap->sibling; 
  18.     else pre_y->sibling = y->sibling; 
  19.  
  20.     //将x节点剩下的子女倒序排列,组成一个新的二项堆 
  21.     BinHeapNode *H2 = NULL,*p=NULL; 
  22.     x = y->leftChild; 
  23.     //这里也就是将x节点的下一层改造成一个新的二项堆 
  24.     while(x != NULL) 
  25.     { 
  26.         p = x; 
  27.         x = x->sibling; 
  28.         p->sibling = H2; 
  29.         H2 = p; 
  30.         p->parent = NULL;//由于x节点删除了就没有parent 
  31.     } 
  32.     //与之前的二项堆合并 
  33.     heap = BinHeapUnion(heap,H2); 
  34.     return y; 
//抽取具有最小关键字的节点
BinHeapNode* BinHeapExtractMin(BinHeapNode *&heap)
{
    BinHeapNode *pre_y=NULL,*y=NULL,*x=heap;
    int min = 1<<30;
    while(x != NULL)
    {
        if(x->key < min)
        {
            min = x->key;
            pre_y = y;
            y = x;
        }
        x = x->sibling;//向根表走
    }//找到最小关键字的节点
    if(y == NULL) return y;//空
    //删除最小关键字节点
    if(pre_y == NULL) heap = heap->sibling;
    else pre_y->sibling = y->sibling;

    //将x节点剩下的子女倒序排列,组成一个新的二项堆
    BinHeapNode *H2 = NULL,*p=NULL;
    x = y->leftChild;
    //这里也就是将x节点的下一层改造成一个新的二项堆
    while(x != NULL)
    {
        p = x;
        x = x->sibling;
        p->sibling = H2;
        H2 = p;
        p->parent = NULL;//由于x节点删除了就没有parent
    }
    //与之前的二项堆合并
    heap = BinHeapUnion(heap,H2);
    return y;
}
5)减小关键字的值
该过程就是更新节点的值,将某一节点的关键字值减小为一个新值K,如果K大于当前节点的关键字值,那么报错。但是注意需要维护最小堆的性质。
BINOMIAL-HEAP-DECREASE-KEY(H, x, k)
1 if k > key[x]
2    then error "new key is greater than current key"
3 key[x] ← k
4 y ← x
5 z ← p[y]
6 while z ≠ NIL and key[y] < key[z]
7     do exchange key[y] ↔ key[z]
8        ▸ If y and z have satellite fields, exchange them, too.
9        y ← z
10        z ← p[y]
  1. //减小关键字的值,将某一节点x的值key减小为一个新值key 
  2. void BinHeapDecreaseKey(BinHeapNode *heap,BinHeapNode *x,int key) 
  3.     if(key > x->key) return
  4.     x->key = key; 
  5.     BinHeapNode *z = NULL,*y=NULL; 
  6.     //需要维护最小堆的结构 
  7.     y = x; 
  8.     z = x->parent; 
  9.     while(z != NULL && z->key > y->key) 
  10.     { 
  11.         swap(z->key,y->key); 
  12.         y = z; 
  13.         z = y->parent;//这里只需要向父节点比较 
  14.     } 
//减小关键字的值,将某一节点x的值key减小为一个新值key
void BinHeapDecreaseKey(BinHeapNode *heap,BinHeapNode *x,int key)
{
    if(key > x->key) return;
    x->key = key;
    BinHeapNode *z = NULL,*y=NULL;
    //需要维护最小堆的结构
    y = x;
    z = x->parent;
    while(z != NULL && z->key > y->key)
    {
        swap(z->key,y->key);
        y = z;
        z = y->parent;//这里只需要向父节点比较
    }
}
6)删除一个关键字
BINOMIAL-HEAP-DELETE(H, x)
1  BINOMIAL-HEAP-DECREASE-KEY(H, x, -∞)
2  BINOMIAL-HEAP-EXTRACT-MIN(H)
可以看出,删除的原理非常简单,把关键字减小,让它到达根节点,然后剔除最小值即可。


  1. //找出一个关键字 
  2. BinHeapNode* BinHeapFind(BinHeapNode *&heap, int key) 
  3.     BinHeapNode *p = NULL, *x = NULL; 
  4.     p = heap; 
  5.     while (p != NULL) 
  6.     { 
  7.         if (p->key == key) 
  8.         { 
  9.             return p; 
  10.         } 
  11.         else 
  12.         { 
  13.             if((x = BinHeapFind(p->leftChild, key)) != NULL)//一直向最左孩子走 
  14.             { 
  15.                 return x; 
  16.             } 
  17.             p = p->sibling; 
  18.         } 
  19.     } 
  20.     return NULL; 
  21.  
  22. //删除一个关键字 
  23. //删除的原理非常简单,把关键字减小,让它到达根节点,然后剔除最小值即可 
  24. BinHeapNode* BinHeapDelete(BinHeapNode * &heap, int key) 
  25.     BinHeapNode * x = NULL; 
  26.     x = BinHeapFind(heap, key);//找到该关键字节点 
  27.     if (x != NULL) 
  28.     { 
  29.         BinHeapDecreaseKey(heap, x, -(1<<30)); 
  30.         return BinHeapExtractMin(heap); 
  31.     } 
  32.     return x; 
//找出一个关键字
BinHeapNode* BinHeapFind(BinHeapNode *&heap, int key)
{
    BinHeapNode *p = NULL, *x = NULL;
    p = heap;
    while (p != NULL)
    {
        if (p->key == key)
        {
            return p;
        }
        else
        {
            if((x = BinHeapFind(p->leftChild, key)) != NULL)//一直向最左孩子走
            {
                return x;
            }
            p = p->sibling;
        }
    }
    return NULL;
}

//删除一个关键字
//删除的原理非常简单,把关键字减小,让它到达根节点,然后剔除最小值即可
BinHeapNode* BinHeapDelete(BinHeapNode * &heap, int key)
{
    BinHeapNode * x = NULL;
    x = BinHeapFind(heap, key);//找到该关键字节点
    if (x != NULL)
    {
        BinHeapDecreaseKey(heap, x, -(1<<30));
        return BinHeapExtractMin(heap);
    }
    return x;
}

上面代码拼接起来就可以运行了,就不贴完整的程序的了。
二项堆的代码还是比较难写的,但是理解起来还是挺简单的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值