二项堆

原文:http://blog.csdn.net/acceptedxukai/article/details/6951922


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

二项树

二项树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:

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

对二项堆的操作

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
[cpp]  view plain copy print ?
  1. //返回最小关键字的指针  
  2. BinHeapNode* BinHeapMin(BinHeapNode *heap)  
  3. {  
  4.     BinHeapNode *y = NULL,*x = heap;  
  5.     int min = 1<<30;  
  6.     //由于符合最小堆的性质,所有最小值就在根节点那一行  
  7.     while(x != NULL)  
  8.     {  
  9.         if(x->key < min)  
  10.         {  
  11.             y = x;  
  12.             min = x->key;  
  13.         }  
  14.         x = x->sibling;//向右边走,到另一个二项树的根  
  15.     }  
  16.     return y;  
  17. }  

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如下

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


[cpp]  view plain copy print ?
  1. //将以H1为根的树和以H2为根的树连接,使H2变成H1的父节点  
  2. void BinLink(BinHeapNode *&H1,BinHeapNode *&H2)  
  3. {  
  4.     H1->parent = H2;  
  5.     H1->sibling = H2->leftChild;//向右连接H2的最左孩子  
  6.     H2->leftChild = H1;//更新H2的最左孩子  
  7.     H2->degree++;//H2的degree++  
  8. }  
BINOMIAL-HEAP-MERGE ,将H1和H2的根表合并成一个按度数的单调递增次序排列的链表
[cpp]  view plain copy print ?
  1. //将二项堆H1和H2的根表合并成一个按度数(degree)单调递增次序的链表,合并到H3然后给heap  
  2. //类似于归并排序过程  
  3. BinHeapNode* BinHeapMerge(BinHeapNode *&H1,BinHeapNode *&H2)  
  4. {  
  5.         BinHeapNode *heap = NULL, *fHeap=NULL,*sHeap=NULL,*pre_H3=NULL,*H3=NULL;  
  6.         fHeap = H1;  
  7.         sHeap = H2;  
  8.         while(fHeap != NULL && sHeap != NULL)  
  9.         {  
  10.             if(fHeap->degree <= sHeap->degree)  
  11.             {  
  12.                 H3 = fHeap;  
  13.                 fHeap=fHeap->sibling;//向右走  
  14.             }  
  15.             else  
  16.             {  
  17.                 H3 = sHeap;  
  18.                 sHeap = sHeap->sibling;//向右走  
  19.             }  
  20.             if(pre_H3 == NULL)//第一次  
  21.             {  
  22.                 pre_H3 = H3;//把首节点给pre_H3  
  23.                 heap = H3;//给heap  
  24.             }  
  25.             else  
  26.             {  
  27.                 pre_H3->sibling = H3;//pre_H3->sibling = H3  
  28.                 pre_H3 = H3;  
  29.             }  
  30.         }//while  
  31.         if(fHeap != NULL) H3->sibling = fHeap;  
  32.         else H3->sibling = sHeap;  
  33.         return heap;  
  34. }  
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
[cpp]  view plain copy print ?
  1. BinHeapNode* BinHeapUnion(BinHeapNode *&H1,BinHeapNode *&H2)  
  2. {  
  3.     BinHeapNode *heap = NULL,*pre_x = NULL,*x=NULL,*next_x=NULL;  
  4.     heap = BinHeapMerge(H1,H2);  
  5.     if(heap == NULL) return heap;  
  6.   
  7.     x = heap;  
  8.     next_x = x->sibling;  
  9.     while(next_x != NULL)  
  10.     {  
  11.         //case1: degree[x] != degree[sibling[x]]  
  12.         if((x->degree!=next_x->degree)  
  13.            //case2:degree[x]=degree[next_x]=degree[sibling[next_x]]  
  14.                 ||(next_x->sibling!=NULL && x->degree == next_x->sibling->degree))  
  15.         {//只需要向右移  
  16.             pre_x = x;  
  17.             x = next_x;  
  18.         }  
  19.         //case3:degree[x]=degree[next_x]!=degree[sibling[next_x]]&x->key <= next_x->key  
  20.         //将sibling[x]连接到x上  
  21.         else if(x->key <= next_x->key)  
  22.         {  
  23.             x->sibling = next_x->sibling;  
  24.             BinLink(next_x,x);  
  25.         }  
  26.         else  
  27.         {//将x连接到sibling[x]上  
  28.             if(pre_x == NULL) heap = next_x;  
  29.             else pre_x->sibling = next_x;  
  30.             BinLink(x,next_x);  
  31.             x = next_x;  
  32.         }  
  33.         next_x = x->sibling;  
  34.     }  
  35.     return heap;  
  36. }  

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′)

[cpp]  view plain copy print ?
  1. BinHeapNode* BinHeapInsert(int key,BinHeapNode *heap)  
  2. {  
  3.     BinHeapNode *newHeap = NULL;  
  4.     newHeap = new BinHeapNode;  
  5.     memset(newHeap,0,sizeof(BinHeapNode));//初始化新节点的值,degree,leftChild,sibling等  
  6.     newHeap->key = key;  
  7.     if(heap == NULL) heap = newHeap;  
  8.     else  
  9.     {  
  10.          heap = BinHeapUnion(heap,newHeap);  
  11.          newHeap = NULL;  
  12.          delete newHeap;  
  13.     }  
  14.     return heap;  
  15. }  

4)抽取具有最小关键字的节点

该过程首先需要找到最小关键字的节点,然后将该节点删除,然后将x的子节点倒序排列,重新组成一个二项堆H1,然后与原来的二项堆H合并组成新的二项堆。


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

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]
[cpp]  view plain copy print ?
  1. //减小关键字的值,将某一节点x的值key减小为一个新值key  
  2. void BinHeapDecreaseKey(BinHeapNode *heap,BinHeapNode *x,int key)  
  3. {  
  4.     if(key > x->key) return;  
  5.     x->key = key;  
  6.     BinHeapNode *z = NULL,*y=NULL;  
  7.     //需要维护最小堆的结构  
  8.     y = x;  
  9.     z = x->parent;  
  10.     while(z != NULL && z->key > y->key)  
  11.     {  
  12.         swap(z->key,y->key);  
  13.         y = z;  
  14.         z = y->parent;//这里只需要向父节点比较  
  15.     }  
  16. }  

6)删除一个关键字

BINOMIAL-HEAP-DELETE(H, x)
1  BINOMIAL-HEAP-DECREASE-KEY(H, x, -∞)
2  BINOMIAL-HEAP-EXTRACT-MIN(H)
可以看出,删除的原理非常简单,把关键字减小,让它到达根节点,然后剔除最小值即可。


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

上面代码拼接起来就可以运行了,就不贴完整的程序的了。
二项堆的代码还是比较难写的,但是理解起来还是挺简单的。

你可能感兴趣的:(二项堆)