代码改变世界

二项堆

2012-07-17 16:52  javaspring  阅读(348)  评论(0编辑  收藏  举报

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

二项树

二项树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. }; 

对二项堆的操作

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; 

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++ 
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; 
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; 

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; 

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; 

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.     } 

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; 

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