原文链接:http://dsqiu.iteye.com/blog/1714961
1 定义
二项堆(Binomial Heap):
二项堆(Binomial Heap)是二项树(Binomial Tree)的集合(collection),所以势必要先简要介绍下二项树。关于堆(最小堆)的相关知识,作者已经在堆排序有介绍可以点击查看,这里就不多枚举了。
二项树(Binomial Tree):
二项树(Binomial Tree)是一组多分支树的序列,二项树的递归定义:
- 二项树的第0棵树只有一个结点;
- 二项树的第K棵树的子结点由二项树的前面k-1棵组成的。
-
从以上定义,不难得到下面的结论:
- 二项树的第K棵树有 2k 个结点,高度是 k ;
- 深度为 d 的结点共有
(nd) 个结点(从上图就可以看出结点是按二项分布的(1,3,3,1))
二项堆由一组二项树所构成,这里的二项树需要满足下列条件:
1)H中的每个二项树遵循最小堆的性质。
2)对于任意非负整数k,在H中至多有一棵二项树的根具有度数k。
对于性质2,任意高度最多有一棵二项树,这样就可以用二项树的集合唯一地表示任意大小的二项堆,比如13个结点的二项堆H,13的二进制表示为(1101),故H包含了最小堆有序二项树B3, B2和B0, 他们分别有8, 4, 2, 1个结点,即共有13个结点。如下图(另外:二项堆中各二项树的根被组织成一个链表,称之为根表)
2 实现(基于最小堆)
2.1 二项树的ADT
typedef struct BinHeapNode BinHeapNode;
typedef BinHeapNode* BinHeapNodelink;
typedef BinHeapNode* BinHeap;
struct BinHeapNode
{
Item key;
int degree;
BinHeapNodelink parent;
BinHeapNodelink leftChild;
BinHeapNodelink sibling; //兄弟
};
2.2 创建二项堆
BinHeap creatBinHeap()
{
BinHeap heap = (BinHeap)malloc(sizeof(*heap));
if(heap == NULL)
{
printf("Memory is not sufficient\n");
exit(1);
}
return heap;
}
2.3 找最小元素
由于每一个二项树都满足最小堆的性质,所以每个二项树的最小关键字一定在根结点,故只需遍历比较根表的情况就可以。
BinHeap findBinHeapMin(BinHeap heap)
{
if(heap == NULL)
return NULL;
BinHeapNodelink h = heap;
BinHeapNodelink min_node = h;
Item min_key = h->key;
while((h = h->sibling) != NULL)
{
if(h->key > min_key)
{
min_node = h;
min_key = h->key;
}
}
return min_node;
}
2.4 合并两个二项堆
合并两个二项堆有三个函数:
BINOMIAL-Pair,连接操作,即将两棵根节点度数相同的二项树Bk-1连接成一棵Bk。
BINOMIAL-HEAP-MERGE ,将H1和H2的根表合并成一个按度数的单调递增次序排列的链表。
BINOMIAL-HEAP-UNION,反复连接根节点的度数相同的各二项树。
合并操作分为两个阶段:
第一阶段:执行BINOMIAL-HEAP-MERGE,将两个堆H1和H2的根表合并成一个链表H,它按度数排序成单调递增次序。MERGE的时间复杂度O(logn)。n为H1和H2的结点总数。(对于每一个度数值,可能有两个根与其对应,所以第二阶段要把这些相同的根连起来)。
第二阶段:将相等度数的根连接起来,直到每个度数至多有一个根时为止。执行过程中,合并的堆H的根表中至多出现三个根具有相同的度数。(MERGE后H中至多出现两个根具有相同的度数,但是将两个相同度数的根的二项树连接后,可能与后面的至多两棵二项树出现相同的度数的根,因此至多出现三个根具有相同的度数)
第二阶段根据当前遍历到的根表中的结点x,分四种情况考虑:
Case1:degree[x] != degree[sibling[x]]。此时,不需要做任何变化,将指针向根表后移动即可。(下图示a)
Case2:degree[x] == degree[sibling[x]] == degree[sibling[sibling[x]]]。此时,仍不做变化,将指针后移。(下图示b)
Case3 & Case4:degree[x] = degree[sibling[x]] != degree[sibling[sibling[x]]] (下图示c和d)
Case3:key[x] <= key[sibling[x]]。此时,将sibling[x]连接到x上。
Case4:key[x] > key[sibling[x]]。此时,将x连接到sibling[x]上。
复杂度:O(logn), 四个过程变化情况:
//=======================两个二项队列的连接============================
BinHeap BinHeapUnion(BinHeap H1, BinHeap H2)
{
BinHeap heap = BinHeapMerge(H1, H2);
if(heap == NULL)
return NULL;
BinHeap x, x_prev, x_next;
x_prev = NULL;
x = heap;
x_next = x->sibling;
//将相同度数的二项堆合并成一个二项堆 B_k -> B_k+1
while(x_next != NULL)
{
//case 1:相邻不等,不作变化指针后移
//case 2:连续3个相等,不作变化指针后移(下一次循环处理后2个相等的节点)
if(x->degree != x_next->degree || (x_next->sibling!=NULL && x_next->degree == x_next->sibling->degree) )
{
x_prev = x;
x = x_next;
}
//case 3、4:相邻2个相等,和第3个不等
else
{
//case 3: 将x_next连到x
if(x->key <= x_next->key)
{
x->sibling = x_next->sibling;
BinHeapPair(x_next, x);
}
//case 4: 将x连到x_next
else
{
if(x_prev == NULL)
heap = x_next;
else
x_prev->sibling = x_next;
BinHeapPair(x, x_next);
x = x_next;
}
}//case 3&4
x_next = x->sibling;
}//while
return heap;
}
//将2个二项队列合并成度数单调递增的一个二项队列
BinHeap BinHeapMerge(BinHeap H1, BinHeap H2)
{
BinHeap H3 = NULL, H3_prev = NULL, heap = NULL;//heap用于指向新的二项队列, H3用作heap的索引指针
BinHeap h1, h2;
h1 = H1, h2 = H2;
if(H1 != NULL && H2 != NULL)
{
while(h1 != NULL && h2 != NULL)
{
if(h1->degree < h2->degree)
{
H3 = h1;
h1 = h1->sibling;
}
else
{
H3 = h2;
h2 = h2->sibling;
}
if(H3_prev == NULL)
{
H3_prev = H3;
heap = H3;
}
else
{
H3_prev->sibling = H3;
H3_prev = H3;
}
}//while
if(h1 == NULL)
H3_prev->sibling = h2;
else if(h2 == NULL)
H3_prev->sibling = h1;
}
if(H1 == NULL)
{
heap = H2;
}
if(H2 == NULL)
{
heap = H1;
}
return heap;
}
//连接2个度数k相同的二项树成度数为k+1的一棵树,将H1 连接到 H2上
BinHeap BinHeapPair(BinHeap H1, BinHeap H2)
{
H1->parent = H2;
H1->sibling = H2->leftChild;
H2->leftChild = H1;
H2->degree++;
return H2;
}
2.5 插入一个结点
先创建只有该结点的二项堆,然后在与原来的二项堆合并。
//用数组元素创建二项队列
BinHeap CreatHeapWithArray(Item a[], int n)
{
BinHeap heap = NULL;
BinHeap newheap = NULL;
for(int i = 0; i < n; i++)
{
newheap = (BinHeap)malloc(sizeof(*newheap));
if(newheap == NULL)
{
printf("Memory is not sufficient\n");
exit(1);
}
memset(newheap, 0, sizeof(*newheap));
newheap->key = a[i];
if(heap == NULL)
heap = newheap;
else
{
heap = BinHeapUnion(heap, newheap);
newheap = NULL;
}
}
return heap;
}
2.6 删除最小
从根表中找到最小关键字的结点,将以该结点为根的整棵二项树从堆取出,删除取出的二项树的根,将其剩下的子女倒序排列,组成了一个新的二项堆,再与之前的二项堆合并。
BinHeap BinHeapDelMin(BinHeap H1)
{
if (H1 == NULL)
return NULL;
BinHeap p, min, min_prev;
p = H1;
min_prev = NULL;
min = H1;
Item key = min->key;
while (p->sibling != NULL)
{
if(p->sibling->key < key)
{
min_prev = p;
min = p->sibling;
key = p->sibling->key;
}
p = p->sibling;
}
BinHeap heap = H1;
if(H1 == min)
heap = min->sibling;
else
min_prev->sibling = min->sibling;
BinHeap x_prev = NULL, x = NULL; //
BinHeap H2 = NULL;
p = NULL;
x = min->leftChild;
while (x != NULL)
{
p = x;
x = x->sibling;
p->sibling = H2;
H2 = p;
p->parent = NULL;
}
heap = BinHeapUnion(heap, H2);
return min;
}