虽然左式堆每次操作花费O(logN),这有效的支持了合并,插入和DeleteMin,但还是有改进的余地,因为我们知道,二叉堆以每次操作花费常数平均时间支持插入。二项队列支持所有这种操作,每次操作的最坏情形运行时间为O(logN),而插入操作平均花费常数时间。
二项队列结构
二项队列不同于左式堆和二叉堆等优先队列的实现之处在于,一个二项队列不是一棵堆序的树,而是堆序树的集合,即森林。堆序树中的每棵树都是由约束形式的,叫做二项树。每一个高度上至多存在一棵二项树。高度为0的二项树是一颗单节点树,高度为k的二项树Bk通过将一棵二项树Bk-1附接到另一颗二项树Bk-1的根上而构成。图1显示二项树B0,B1,B2,B3以及B4。
图1:高度为0,1,2,3,4的五棵二项树
从图中可以看出,二项树Bk由一个带有儿子B0,B1,B2,……Bk-1 的根组成。高度为k的二项树恰好有2^k个节点。如果我们把堆序施加到二项树上并允许任意高度上最多有一棵二项树,那么我们便能够用二项树的集合惟一的表示任意大小的优先队列。例如,大小为13的优先队列可以用森林B3,B2,B0表示,可以把这种表示写成1101,它不仅以二进制表示了13,而且说明了B3,B2,B0出现,而B1没出现。
二项队列操作
此时,因为二项队列也满足了堆序性,故最小元可以通过搜索所有的树根来找出,由于最多有logN棵不同的树,因此最小元可以在O(logN)中找到。
合并操作基本上是通过将两个队列加到一起来完成的。令H1,H2是两个二项队列,H3是新的二项队列。由于H1没有高度为0的二项树而H2有,因此就用H2中高度为0的二项树作为H3的一部分。因为H1,H2都有高度为1的二项树,将它们合并,让大的根成为小的根的子树,从而建立高度为2的二项树,见图3所示。这样,H3将没有高度为1的二项树。先存在3棵高度为2的二项树,即H1,H2原有的两棵树及由上一步合并得到的一棵二项树。我们将一棵高度为2的二项树放入到H3中,并合并其他两个二项树,得到一棵高度为3的二项树。由于H1,H2都没有高度为3的二项树,因此该二项树就成为H3的一部分,合并结束,见图4所示:
图2:两个二项队列H1,H2 图3:H1和H2中两棵B1树合并
图3:最终结果
插入实际上就是合并的特殊情形,我们只要创建一棵单节点的树并执行一次合并,这种操作的最坏情形运行时间也是O(logN)。更准确的说,如果元素将要插入的那个优先队列不存在的最小的二项树是Bi,那么运行时间与i+1成正比。图4和图5演示通过依序插入1到7来构成一个二项队列。
DeleteMin可以通过首先找出一棵具有最小根的二项树来完成,令该树为Bk,并令原有的优先队列为H,我们从H的树的森林中除去二项树Bk,形成新的二项队列H1。再出去Bk的根,得到一些二项树B0,B1,B2,….Bk-1,它们共同形成了优先队列H2,合并H1,H2,操作结束。
图6,图7和图8演示了对H3执行一次DeleteMin。
图6:二项队列H3,最小元为12
图7:二项队列H‘,包含除B3外H3中所有的二项树 图8:二项队列H'',除去12之后的B3
图9:DeleteMin(H3)的结果
二项队列实现
DeleteMin操作需要快四找出根的所有子树的能力,因此,需要一般树的标准表示方法:每个节点的儿子都在一个链表中,每个节点都有一个指向它的第一个儿子的指针。该操作还要求,诸儿子按照他们的子树大小排序。当两棵树被合并时,其中的一棵树最为儿子被加到另一棵树上。由于这棵新树将是最大的子树,因此,以大小递减的方式保持这些子树很有意义。
总之,二项树的每一个节点将包括:数据,指向第一个儿子的指针以及右兄弟。二项树中的诸儿子以递减次序排序。
图10解释如何表示H3中的二项队列。图10 二项队列H3的表示方式
为了合并两个二项队列,我们需要一个例程来合并两个大小相同的二项树。图11指出两个大小相同的二项树合并时指针是如何变化的:
图11: 合并两颗二项树
详细代码:
头文件:
typedef long ElementType;
#define Infinity (30000L)
#ifndef _BinHeap_H
#define _BinHeap_H
#define MaxTrees (14) //二项队列中的二项树高度最大为13;
#define Capacity (16383) //高度0,1,2,3,...,13的二项树节点数目之和
struct BinNode; //二项树节点
typedef struct BinNode *BinTree; //二项树
struct Collection;
typedef struct Collection *BinQueue;
BinQueue Initialize(void);
void Destroy( BinQueue H);
BinQueue MakeEmpty(BinQueue H);
BinQueue Insert(ElementType Item, BinQueue H);
ElementType DeleteMin(BinQueue H);
BinTree CombineTrees( BinTree T1, BinTree T2 ); //合并两棵相同大小的树
BinQueue Merge(BinQueue H1, BinQueue H2);
ElementType FindMin(BinQueue H);
int IsEmpty(BinQueue H);
int IsFull(BinQueue H);
#endif
源文件:
#include "binomial.h"
#include <stdio.h>
#include <stdlib.h>
typedef struct BinNode *Position;
struct BinNode
{
ElementType Element;
Position LeftChild;
Position NextSibling;
};
struct Collection
{
int CurrentSize;
BinTree TheTrees[MaxTrees]; //数组,该数组每个元素都是一棵二项树
};
BinQueue Initialize()
{
BinQueue H;
int i = 0;
H = malloc(sizeof(struct Collection));
if (H == NULL){
printf("Out of space!!\n");
return NULL;
}
H->CurrentSize = 0; //设置二项队列初始大小为0,每棵二项树均为NULL
for (i = 0; i < MaxTrees; ++i){
H->TheTrees[i] = NULL;
}
return H;
}
static void DestroyTree(BinTree T)
{
//递归删除二项树的所有节点
if (T != NULL){
DestroyTree(T->LeftChild);
DestroyTree(T->NextSibling);
free(T);
}
}
void Destroy(BinQueue H)
{
//释放二项队列所占空间:通过删除所有的二项树完成
int i = 0;
for (i = 0; i < MaxTrees; ++i){
DestroyTree(H->TheTrees[i]);
}
}
BinQueue MakeEmpty(BinQueue H)
{
int i = 0;
Destroy(H); //首先释放H所占空间
for (i = 0; i < MaxTrees; ++i){
H->TheTrees[i] = NULL;
}
H->CurrentSize = 0; //二项队列当前大小
return H;
}
BinQueue Insert(ElementType Item, BinQueue H)
{
BinTree NewNode; //二项树B0
BinQueue OneItem; //只有B0的二项队列
NewNode = malloc(sizeof(struct BinNode));
if (NewNode == NULL){
printf("Out of space!\n");
return H;
}
NewNode->Element = Item;
NewNode->LeftChild = NewNode->NextSibling = NULL;
OneItem = Initialize();
OneItem->CurrentSize = 1;
OneItem->TheTrees[0] = NewNode;
return Merge(H, OneItem); //合并单节点的二项树构成的二项队列与H
}
ElementType FindMin(BinQueue H)
{
int i = 0;
ElementType MinItem;
if (IsEmpty(H)){
printf("Empty binomial queue");
return -1;
}
MinItem = Infinity;
//遍历二项队列中的所有二项树,比较它们的根
for (i = 0; i < MaxTrees; ++i){
if (H->TheTrees[i] && H->TheTrees[i]->Element < MinItem){
MinItem = H->TheTrees[i]->Element;
}
}
return MinItem;
}
int IsEmpty(BinQueue H)
{
return H->CurrentSize == 0; //currentsize存放二项队列中节点的个数
}
int IsFull(BinQueue H)
{
return H->CurrentSize == Capacity;
}
BinTree CombineTrees( BinTree T1, BinTree T2 )
{
//合并相同大小的两颗二项树
if (T1 == NULL)
return T2;
else if (T2 == NULL)
return T1;
if (T1->Element > T2->Element)
return CombineTrees(T2, T1);
//根大的树做为根小的树的左儿子
T2->NextSibling = T1->LeftChild;
T1->LeftChild = T2;
return T1;
}
BinQueue Merge(BinQueue H1, BinQueue H2)
{
BinTree T1, T2, Carry = NULL;
int i = 0, j = 0;
//首先判断合并是否会超出二项队列限制的大小
if (H1->CurrentSize + H2->CurrentSize > Capacity){
printf("Merge would exceed capacity!\n");
return H1;
}
H1->CurrentSize += H2->CurrentSize;
//遍历H1,H2中所有的二项树
for (i = 0, j = 1; j <= H1->CurrentSize; ++i, j *= 2){
T1 = H1->TheTrees[i];
T2 = H2->TheTrees[i];
//若T1为空,!!T1则为0,否则为1
switch(!!T1 + 2* (!!T2) + 4 * (!!Carry)){
case 0:
case 1:
break;
case 2:
//只有T2存在,直接将T2放入二项队列H1中对应的位置;
H1->TheTrees[i] = T2;
H2->TheTrees[i] = NULL;
break;
case 3:
//T1与T2均存在,合并相同大小的二项树
Carry = CombineTrees(T1, T2);
H1->TheTrees[i] = NULL;
H2->TheTrees[i] = NULL;
break;
case 4:
//由上一步合并而得的二项树作为二项队列H1的一部分
H1->TheTrees[i] = Carry;
Carry = NULL;
break;
case 5:
Carry = CombineTrees(T1, Carry);
H1->TheTrees[i] = NULL;
break;
case 6:
Carry = CombineTrees(T2, Carry);
H2->TheTrees[i] = NULL;
break;
case 7:
H1->TheTrees[i] = Carry;
Carry = CombineTrees(T1, T2);
H2->TheTrees[i] = NULL;
break;
}
}
return H1;
}
ElementType DeleteMin(BinQueue H)
{
int i = 0, j = 0;
int MinTree; //用来存放根最小的二项树的高度
BinQueue DeletedQueue;
Position DeletedTree, OldRoot;
ElementType MinItem;
if (IsEmpty(H)){
printf("Empty binomial queue!\n");
return -1;
}
MinItem = Infinity;
//遍历二项队列,找出根元素最小的二项树
for (i = 0; i < MaxTrees; ++i){
if (H->TheTrees[i] && H->TheTrees[i]->Element < MinItem){
MinItem = H->TheTrees[i]->Element;
MinTree = i;
}
}
DeletedTree = H->TheTrees[MinTree];
OldRoot = DeletedTree;
DeletedTree = DeletedTree->LeftChild;
free(OldRoot); //删除根元素;
DeletedQueue = Initialize();
//将1左移MinTree位,即得到高度为MinTree的二项树的大小
//因为高度为k的二项树的大小是2^k,减1是因为删除了根
DeletedQueue->CurrentSize = (1 << MinTree) -1;
//将删除根后的各个子树构成新的二项队列
for (j = MinTree - 1; j >= 0; --j){
DeletedQueue->TheTrees[j] = DeletedTree;
DeletedTree = DeletedTree->NextSibling;
DeletedQueue->TheTrees[j]->NextSibling = NULL;
}
H->TheTrees[MinTree] = NULL;
H->CurrentSize -= DeletedQueue->CurrentSize + 1; //新二项队列的大小;
Merge(H, DeletedQueue);
return MinItem;
}