Fibonacci Heap(简称F-Heap)是一种基于二项堆的非常灵活的数据结构。它与二项堆不同的地方在于:
1)root list和任何结点的child list使用双向循环链表,而且这些lists中的结点不再有先后次序(Binomial Heap中root list的根结点按degree从小到大顺序,child list的结点按degree从大到小顺序);
2)二项堆中任何一颗Binomial Tree中根结点的degree是最大的,而F-Heap中由于decrease-key操作(cut和cascading cut)的缘故,并不能保证根结点的degree最大;
3)二项堆中任何结点(degree等于k的)为根的子树中,结点总数为2^k;F-Heap中相应的结点总数下界为F{k+2},上界为2^k(如果没有Extract-Min和Delete两类操作的话)。其中F{k+2}表示Fibonacci数列(即0,1,1,2,3,5,8,11...)中第k+2个Fibonacci数,第0个Fibonacci数为0,第1个Fibonacci数为1。注意不像二项堆由二项树组成那样,F-Heap的root list中的每棵树并不是Fibonacci树(Fibonacci树属于AVL树),而F-Heap名称的由来只是因为Fibonacci数是结点个数的一个下界值。
4)基于上面的区别,若F-Heap中结点总数为n,那么其中任何结点(包括非根结点)的degree最大值不超过D(n) = floor(lgn/lg1.618),这里1.618表示黄金分割率(goldren ratio),即方程x^2=x+1的一个解。所以在Extract-Min的consolidate操作之后,root list中的结点最多有D(n)+1。而二项堆中degree最大值不超过floor(lgn),从而root list中最多有floor(lgn)+1颗二项树。
5)另外一个与二项堆的最大不同之处在于:F-Heap是一种具有平摊意义上的高性能数据结构。除了Extract-Min和Delete两类操作具有平摊复杂度O(lgn),其他的操作(insert,union,find-min,decrease-key)的平摊复杂度都是常数级。因此如果有一系列的操作,其中Extract-min和delete操作个数为p,其他操作个数为q,p<q,那么总的平摊复杂度为O(p + q.lgn)。达到这个复杂度的原因有以下几点,第一,root list和任何结点的child list中使用了双向循环链表;第二,union和insert操作的延迟合并,从而在所有的可合并堆中,F-heap的合并开销O(1)最小的;第三,decrease-key中cut和cascading cut的巧妙处理(即任何非根结点最多失去一个孩子)。
以下是基于CLRS第三版的伪代码的Fibonacci Heap的实现。以下几点值得注意一下:
1)Decrease-Key操作中通过添加变量cascade消除CLRS中Cascading Cut函数的tail recursion;
2)Extract-Min的consolidate函数中每处理一个root结点就将该结点从root list中删除,然后在寻找新的min结点时按照degree从小到大次序(实际上在F-Heap中不用关心节点之间的相对顺序)恢复root list;
3)consolidate函数中的link操作和二项堆中的操作类似,只不过这里不用考虑child list中结点的顺序;
4)Extract-Min中涉及mark属性的代码:在Extract-Min中将min的所有孩子结点添加到root list时不需要清除mark属性,等到consolidate的link操作以及寻找新的min结点时再分别设置:linked child的mark = false,root的mark=false。
/**
*
* Fibonacci Heap
*
* Copyright (c) 2011 ljs (http://blog.csdn.net/ljsspace/)
* Licensed under GPL (http://www.opensource.org/licenses/gpl-license.php)
*
* @author ljs
* 2011-09-05
*
*/
public class FibonacciHeap {
static class Node{
private int key;
private Node parent=null;
private int degree=0;
private Node child=null;
private Node left;
private Node sibling;
private boolean mark=false;
public Node(int key){
this.key = key;
}
public String toString(){
return this.key + "(degree=" + this.degree + ",mark=" + this.mark + ")";
}
}
//the head of root list
private Node min;
private int n;
//insert a single node
public void insert(Node node){
addToRootList(node);
n++;
}
private void addToRootList(Node node){
if(min==null){
node.left=node.sibling=node;
min = node;
}else{
//insert node in root list as node's left neighbor
Node prev = min.left;
prev.sibling = node;
node.sibling = min;
min.left = node;
node.left = prev;
if(node.key<min.key)
min = node;
}
}
public Node findMin(){
return this.min;
}
//rhs is the min node of H'
public void union(FibonacciHeap h){
Node rhs = h.min;
if(rhs==null) return;
//concatenate the root list of both heaps: insert rhs as the right neighbor of min
if(this.min == null){
this.min = rhs;
}else{
Node nextH1 = min.sibling;
Node lastH2 = rhs.left;
min.sibling = rhs;
lastH2.sibling = nextH1;
nextH1.left = lastH2;
rhs.left = min;
if(this.min.key>rhs.key){
min = rhs;
}
}
n += h.n;
}
public Node extractMin(){
Node z = this.min;
if(z!=null){
//insert z's children in the root list
Node x = z.child;
while(x!=null){
Node next = x.sibling;
this.addToRootList(x);
x.parent = null;
//x.mark = false; //delay updating mark until linking and finding new min
x = (next != z.child)?next:null;
}
//remove z from root list
n--;
Node prev = z.left;
if(prev==z){
//z is the only node in root list
min = null;
}else{
Node next = z.sibling;
prev.sibling = next;
next.left = prev;
//set min to a temporary value as an entry pointer of the heap,
//later we will update it
this.min = next;
consolidate();
}
}
return z;
}
private void consolidate(){
double goldratio = (Math.sqrt(5)+1)/2.0;
int maxdegree = (int)(Math.log(n) / Math.log(goldratio));
Node[] A = new Node[maxdegree+1];
//iterate all nodes in root list
Node w = this.min;
while(w!=null){
Node prev = w.left;
Node next = w.sibling;
Node x = w;
int d = x.degree;
while(A[d] != null){
Node y = A[d];
//link x and y
if(x.key<y.key){ //y is added as a child of x
x = link(x,y);
}else{//x is added as a child of y
x = link(y,x);
}
A[d] = null;
d++; //when two trees with same degree are linked, the degree increases by 1.
}
A[d] = x;
//remove w from root list
if(prev == w){
//w is the only node in root list
w = null; //set condition to exit while
}else{
prev.sibling = next;
next.left = prev;
w = next;
}
}
min = null;
//reconstruct root list from A[]
for(int d=0;d<=maxdegree;d++){
if(A[d] != null){
A[d].mark = false; //root node is unmarked
addToRootList(A[d]);
}
}
}
private Node link(Node small,Node large){
Node child = small.child;
if(child==null){
small.child = large;
large.left = large.sibling = large;
}else{
//make large node as a child of small node: insert large as a left neighbor of small.child
Node childPrev = child.left;
childPrev.sibling = large;
large.sibling = child;
child.left = large;
large.left = childPrev;
}
small.degree++;
large.parent = small;
large.mark = false; //large is a child of small, so its mark is reset
return small;
}
public void decreaseKey(Node x,int k) throws Exception{
if(x.key<k) throw new Exception("key is not decreased!");
x.key = k;
Node p = x.parent;
boolean cascade = false;
while(p!=null){
if(cascade || x.key<p.key){
cut(x,p);
if(p.mark){ //cascading cut
x = p;
p = x.parent;
cascade = true;
}else{
if(p.parent != null){ //p is not root
p.mark = true;
}
break;
}
}
}
//if p is null, then x is root.
//Root node doesn't change when decreased key(thanks to the above exception checking).
//update min: only x's key is changed, other's roots created via cascading cut has no effect on min
if(k<this.min.key){
min = x;
}
}
private void cut(Node x,Node parent){
if(x == x.sibling){ //x is the only child of parent
parent.child=null;
}else{
Node prev = x.left;
Node next = x.sibling;
//remove x from child list
prev.sibling = next;
next.left = prev;
parent.child=next;
}
parent.degree--;
x.parent = null;
x.mark = false;
//add x to root list
addToRootList(x);
}
public void delete(Node x) throws Exception{
decreaseKey(x,Integer.MIN_VALUE);
this.extractMin();
}
public void print(){
System.out.format("F-Heap(n=%d):%n",this.n);
Node h = this.min;
while(h!=null){
this.print(0, h);
h = (h.sibling!=min)?h.sibling:null;
}
}
private void print(int level, Node node){
for (int i = 0; i < level; i++) {
System.out.format(" ");
}
System.out.format("|");
for (int i = 0; i < level; i++) {
System.out.format("-");
}
//boolean isChildLink = (node.parent!=null && node.parent.child == node);
System.out.format("%d%s%n", node.key,node.mark?"(x)":"");
Node child = node.child;
while(child!=null){
print(level + 1, child);
child = (child.sibling!=node.child)?child.sibling:null;
}
}
public static void main(String[] args) throws Exception {
//heap1
FibonacciHeap fheap1 = new FibonacciHeap();
Node node5=null,node14=null,node15=null;
for(int i=0;i<17;i++){
Node node = new Node(i);
fheap1.insert(node);
if(i==5) node5=node;
if(i==14) node14=node;
if(i==15) node15=node;
}
fheap1.extractMin();
//fheap1.print();
fheap1.decreaseKey(node14, 9);
fheap1.decreaseKey(node15, 8); //cascading cut
//fheap1.print();
fheap1.delete(node5);
fheap1.print();
Node min=fheap1.findMin();
System.out.format("min:%d%n", min.key);
//heap2
System.out.println("");
FibonacciHeap fheap2 = new FibonacciHeap();
int[] B = new int[]{40,39,20,18,41,38,52,3};
Node node52=null,node41=null;
for(int i=0;i<B.length;i++){
Node node = new Node(B[i]);
fheap2.insert(node);
if(i==6) node52 = node;
if(i==4) node41 = node;
}
fheap2.insert(new Node(0));
fheap2.extractMin();
//fheap2.print();
fheap2.decreaseKey(node52, 7);
//fheap2.print();
fheap2.decreaseKey(node41, 21);
//fheap2.print();
fheap2.extractMin();
fheap2.print();
//union heap1 and heap2
fheap1.union(fheap2);
fheap1.print();
min = fheap1.extractMin();
while(min!=null){
System.out.format("%d ",min.key);
min = fheap1.extractMin();
}
}
}
测试输出:
F-Heap(n=15):
|1
|-9(x)
|--10
|--11
|---12
|-2
|-3
|--4
|6
|7
|-8
|8
|-16
|-9
|--13
min:1
F-Heap(n=7):
|7
|-21
|-20
|--39
|18
|38
|-40(x)
F-Heap(n=22):
|1
|-9(x)
|--10
|--11
|---12
|-2
|-3
|--4
|7
|-21
|-20
|--39
|18
|38
|-40(x)
|6
|7
|-8
|8
|-16
|-9
|--13
1 2 3 4 6 7 7 8 8 9 9 10 11 12 13 16 18 20 21 38 39 40