最近在比较Dijkstra+各种优先队列组合的效率。
这个想法源于算法课的作业。作业要我们用现成的库(LEDA)来比较。我比较出来的结果和预期相差甚远,怀疑是不是库的问题。
于是,这次我亲手写了所有的代码,再比较一次。
Binomial Heap虽然经常被各种教科书提到,不过实际应用不是很多。
理由很简单:他没有堆简单,也没有斐波那契堆高效。
然而,优美的结构(虽然不是很容易实现,可能是我写的不好)让它在各个教材中都占有一席之地。
Binomial Heap所有操作复杂度都是O(logn)的。
一个Binomial Heap是一个“二项树”的链表,几乎所有操作都基于merge方法。
每个节点都用“做孩子又兄弟”表示(这样表示似乎可以减少指针的浪费:正常表示方法中大量叶节点都是null,而左孩子右兄弟表示中null出现次数减少了一半,因为叶节点只有一个指针指向null)
以前用vector写过一个Binomial Heap,感觉有些山寨。这次正儿八经的用linked list写了一遍。
merge方法说起来简单,其实却相当难写……不知道有啥简洁的实现方法不。
delete min方法讲起来也很简单,不过merge之前要把chid的链表反向一下,因为child链表中元素的degree是递减的。
目前实验结果表明dijkstra+Binomial Heap的表现几乎和普通堆持平,有时候甚至超过普通堆。
#ifndef __BINOMIAL_HEAP__ #define __BINOMIAL_HEAP__ #include”PQ.h” template<class T> class binomial_heap : public PQ<T>{ private: class node{ public: T key; int degree,idx; node *child,*sibling,*parent; }; typedef pair<T,int> PTI; node *data, *head; node ** id; int alloc_idx; node* new_node(){ return &data[alloc_idx++]; } node* min_node(){ node* res=0; for(node* t=head;t;t=t->sibling) if(!res || t->key < res->key) res=t; return res; } node* link(node*a,node* b){ //assume a->degree==b->degree if(b->key < a->key) swap(a,b); b->sibling=a->child; a->child=b; b->parent=a; a->degree++; return a; } void swap_node(int i,int j){ swap(id[i]->idx,id[j]->idx); swap(id[i]->key,id[j]->key); swap(id[i],id[j]); } node* list_insert(node* tail,node* a){ if(!tail)return a; tail->sibling=a; return a; } node* list_reverse(node* a){ if(!a)return 0; node *prev=a,*next=a->sibling; a->sibling=0; while(next){ a=next; next=a->sibling; a->sibling=prev; prev=a; } return prev; } node* merge(node* a,node* b){ if(!a)return b; if(!b)return a; node *res=0,*tail=0,*carry=0; while(a && b){ if(a->degree > b->degree) swap(a,b); if(a->degree==b->degree){ if(carry){ tail=list_insert(tail,carry); if(!res)res=tail; } node* t1=a->sibling,*t2=b->sibling; carry=link(a,b); carry->sibling=0; a=t1; b=t2; }else{ //a->degree < b->degree if(carry){ if(carry->degree < a->degree){ tail=list_insert(tail,carry); if(!res)res=tail; carry=0; }else{ node* t=a->sibling; carry=link(carry,a); carry->sibling=0; a=t; } }else{ tail=list_insert(tail,a); if(!res)res=tail; a=a->sibling; } } } if(a)swap(a,b); if(carry){ if(b){ while(b && carry->degree==b->degree){ node* t=b->sibling; carry=link(carry,b); carry->sibling=0; b=t; } if(b){ tail=list_insert(tail,carry); if(!res)res=tail; tail=list_insert(tail,b); }else{ tail=list_insert(tail,carry); if(!res)res=tail; } }else{ tail=list_insert(tail,carry); if(!res)res=tail; } }else if(b){ tail=list_insert(tail,b); if(!res)res=tail; } return res; } public: binomial_heap(int n){ head=0; data=new node[n]; id=new node*[n]; alloc_idx=0; } ~binomial_heap(){ delete[] data; delete[] id; } virtual bool empty(){ return !head; } virtual void insert(int i,const T& key){ id[i]=new_node(); id[i]->idx=i; id[i]->key=key; id[i]->degree=0; id[i]->child=id[i]->sibling=id[i]->parent=0; head=merge(head,id[i]); } virtual PTI min(){ //assume non-empty node* res=min_node(); return make_pair(res->key,res->idx); } virtual int delete_min(){ //assume non-empty node *m=head,*pre=0; for(node* p=head;p->sibling;p=p->sibling){ if(p->sibling->key < m->key){ m=p->sibling; pre=p; } } if(m==head){ head=m->sibling; }else{ pre->sibling=m->sibling; } //print(m->child);printf(“!/n”); //print(list_reverse(m->child));printf(“!/n”); head=merge(head,list_reverse(m->child)); return m->idx; } virtual void decrease_key(int i,const T& k){ node* t=id[i]; t->key=k; while(t->parent && k < t->parent->key){ swap_node(t->idx,t->parent->idx); t=t->parent; } } void print(node* a){ for(node* t=a;t;t=t->sibling) printf(“%d “,t->degree); } void print(){ print(head); } }; #endif