每件事物都有其应需而生的目的,既然存在了,一定有其出现的因和果。二项堆的存在,就是因为二叉堆在Union操作上性能不如意而被发明的。二项堆的Union操作只需O(lgn)时间就可以完成两个二项堆的合并(总共n个元素)。
二叉堆是完全二元树(二叉树)或者是近似完全二元树(二叉树)。二叉堆有两种:最大堆和最小堆。最大堆:父结点的键值总是大于或等于任何一个子节点的键值;最小堆:父结点的键值总是小于或等于任何一个子节点的键值。
那么二项堆的定义呢?二项堆是由一组二项树组成,所以先看二项树定义。
二项树Bk是一种递归定义的有序树。二项树B0只包含一个节点。二项树Bk由两颗树Bk-1连接而成:其中一颗树的根式另一个树的根的最左孩子。二项树Bk具有以下性质:
1)共有2k个节点
2)树的高度为k
3)在深度i处恰有个节点,其中i=0,1,…,k
4)根的度数为k,大于任何其他结点的度数,并且如果根的子女从左到右编号为k-1,k-2,…,1,0,子女i是子树的Bi根。
二项堆H是由一组二项树组成,并满足以下性质:
1)H中的每个二项树都遵循最小堆性质:结点的关键字大于或等于其父节点的关键字;
2)对任意非负整数k,在H中至多有一棵二项树的根具有度数k。
这两点总结来说,关系到二项堆中二项树的两点,一是结点度数,另一个是节点关键字值。首先关键字值要有序,父结点要小于子结点,其次,二项堆中二项树的根结点度数要互异(不能有两棵二项树的根的度数是一样)。这是在二项堆操作中一定要守恒的。二项堆中结点的域有:指向父节点的指针p[x]、指向最左孩子的指针child[x]、指向紧右兄弟的指针sibling[x]、结点度数degree[x]、关键字值key[x]。
二项堆操作,如创建、查找最小关键字、删除关键字等都要维持其性质,其中合并两个二项堆需要重点说明其算法和性能。
Fun_Binominal_heap_Union(H1,H2){
Make_Binominal_Heap(H);//建一个新堆
head[H]=Binominal_Heap_Merge(H1,H2);//合并成一个按度数递增次序排列的链表,堆的根按照度数递增作为新堆的根
if head[H]=null then return H;
prev[x]=null;
x=head[H];
next[x]=sibling[x];//第一棵树根作为第一个结点
while next[x] is not null
do if (degree[x] ≠degree[next[x]]
or(sibling[next[x]] is not null and degree[x]=dgree[sibling[next[x]]]
thenprev[x]=x
x=next[x]
else if key[x] =< key[next[x]]
thensibling[x]=sibling[next[x]]
Binoial_link(next[x],x);
else if prev[x]=null
then head[H]=next[x]
else sibling[prev[x]]=next[x]
Binoial_link(x,next[x]);
x=next[x];
next[x]=sibling[x];
return H;
}
函数内,主要是对合并后结点做处理,以确保满足二项堆和二项树性质,可以看到主要对结点的比较就是度数和关键字值。看下两个度数相同的二项树Bk-1合并成一颗树Bk的函数。
Fun_ Binoial_link(x,y){
p[y]=x; //x作为y的父结点
sibling[y]=child[x];//x的孩子成为y的兄弟
child[x]=y;
degree[x]=degree[x]+1;
}
Binominal_heap_Union函数运行时间是O(lgn),其中n是二项堆H1(n1)和H2(n2)中总的结点数。H1至多包含lgn1+1个根,H2至多包含lgn2+1个根,合并后H至多包含2lgn+2,即O(lgn)个根。而执行函数的时间主要是在根结点循环,所以总共至多执行lgn2+2次迭代。
比较有趣的一个操作是当关键字值减少时调整二项堆使其保持性质,也是需要O(lgn)运行时间,要去判断关键字值。那么能否先删除关键字再作为新的关键字插入呢?新关键字插入导论中没有给出,但我想可以作为堆合并来处理,因为一个结点就是一颗二项树或二项堆,所以时间也是O(lgn),但删除关键字的时间也需要O(lgn),总共需要O(2lgn)次,所以还是直接在原来结点调整值,然后保持二项堆性质。
虽然理解了定义和基本操作,但二项堆还没想到具体应用点。