二项堆
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:
- 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
- //返回最小关键字的指针
- 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如下
一个完整的合并操作示意图
- //将以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++
- }
- //将二项堆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;
- }
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
- 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′)
- 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合并组成新的二项堆。
- //抽取具有最小关键字的节点
- 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]
- //减小关键字的值,将某一节点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)
可以看出,删除的原理非常简单,把关键字减小,让它到达根节点,然后剔除最小值即可。- //找出一个关键字
- 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;
- }
上面代码拼接起来就可以运行了,就不贴完整的程序的了。
二项堆的代码还是比较难写的,但是理解起来还是挺简单的。