123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680(******************************************************************************
* *
* Fungi Heap *
* Functional Fibonacci Heap *
* harryk@harryk.dev *
* *
*******************************************************************************)(**
{1:fibheap Fibonacci Heap Implementation }
Simplest Indexed Fibonacci (d-ary) heap
- Every node's key is less(default cmp) than or equal to its children
keys as given by the Entry.compare function
- The minimum|maximum element is always in the root list depending
on your compare function
- Unlike binary heaps, there's no enforced structure
- Duplicates are tolerated as they can be introduced by decrease and
increase operations
- A node can have any number of children
- Children can be added or removed freely
- no redundancy (if 2 nodes are the same their trees are assumed to
be the same)
- For simplicity we don't have a min pointer at the root list but it may
be added in the future, we so have to walk the root list to get the
min
*)moduletypeOrdinal=sig(* The full type *)typet(* How we order in the heap *)typeorder(* How we get the order from a node *)valbind:t->order(* How we compare different elements *)(* total order *)valcompare:t->t->int(* How we compare different orders *)valorder:order->order->intend(* immediately binds a value as its own order *)moduleSurject(Inner:Set.OrderedType):Ordinalwithtypet=Inner.tandtypeorder=Inner.t=structtypet=Inner.ttypeorder=Inner.tletbinde=eletorder=Inner.compareletcompare=Inner.compareendmoduletypeFibHeap=sigtypenodetypeordertypeelts={data:node;mutablechurn:int;index:int;succ:eltslist}typet=eltslistvalempty:tvalis_empty:t->boolvalequal:node->node->boolvalminify:node->node->boolvalmaxify:node->node->boolvaloequal:node->node->intvaldegree:elts->intvalcardinal:t->intvalcollapse:t->nodelistvalchurn_threshold:intrefvalinstance:node->int->eltsvalsingleton:node->tvaldedup:node->eltslist->(eltslist*nodelist)valdedup_idx:node->eltslist->eltslist*(node*int)listvaldupcount:node->t->intvalextract_til:?cmp:(node->node->bool)->(node->bool)->t->nodelistvalto_seq:?cmp:(node->node->bool)->t->nodeSeq.tvalof_seq:?cmp:(node->node->bool)->nodeSeq.t->eltslistvalof_list:?cmp:(node->node->bool)->nodelist->tvalconsolidate:?cmp:(node->node->bool)->t->tvalmem:?cmp:(node->node->bool)->node->t->boolvalinsert:?cmp:(node->node->bool)->node->t->tvaldupinsert:?cmp:(node->node->bool)->node->t->tvalpeek:?cmp:(node->node->bool)->t->eltsvalpeek_opt:?cmp:(node->node->bool)->t->eltsoptionvalmerge:?cmp:(node->node->bool)->elts->elts->eltsvalextract:?cmp:(node->node->bool)->t->(node*t)valextract_opt:?cmp:(node->node->bool)->t->(node*t)optionvalextract_all:?cmp:(node->node->bool)->t->nodelistvalupdate:?cmp:(node->node->bool)->node->elts->eltslist->elts->eltslist->eltslist*eltslist*boolvalincrease:?cmp:(node->node->bool)->node->node->t->tvaldecrease:?cmp:(node->node->bool)->node->node->t->tvalfind:(node->bool)->eltslist->nodevalfind_opt:(node->bool)->eltslist->nodeoptionend(**
{2:create Create a Fibonacci Heap}
{@ocaml[
module F = Heap.MakeFibHeap (struct
(* the main type *)
type t = int
(* how to determine order *)
type order = int
let bind e = e
(* compare to orders *)
let order = Int.compare
(* compare to nodes *)
let compare = Int.compare
end);;
]}
*)moduleMakeFibHeap(Entry:Ordinal):FibHeapwithtypenode=Entry.tandtypeorder=Entry.order=structtypenode=Entry.t;;typeorder=Entry.order;;(* churn tracks the grand-children that have been removed *)typeelts={data:node;mutablechurn:int;index:int;succ:eltslist};;typet=eltslist;;letempty=[];;letis_empty=function|[]->true|_::_->false;;(* internal joining tbl which stores int (degree) -> elts *)lettbl=Hashtbl.create0;;letequallr=(Entry.comparelr)=0;;(* comparator that determines if the root is the smallest or the largest *)letoequallr=(Entry.order(Entry.bindl)(Entry.bindr));;(* max-heapify, root elts are the most of their successors *)letmaxifylr=oequallr=1;;(* min-heapify, root elts are the least of their successors *)letminifylr=oequallr=-1;;(* churn threshold *)letchurn_threshold=ref8;;letinstancepleafidx={data=pleaf;churn=0;index=idx;succ=[]};;letmem?(cmp=minify)pleafptree=letrecfmemtreerem=matchtreewith|[]->(matchremwith|[]->false|branch::left->(matchbranchwith|[]->(matchleftwith|[]->false|l::r->fmemlr)|hd::rest->(* reduce the number of `List.concat` operations by
'batching' lists - this works really good for dense heaps
*)(equalhd.datapleaf)||(ifcmphd.datapleafthenfmemhd.succ(rest::left)elsefmemrest(hd.succ::left))))|hd::tail->(equalhd.datapleaf)||(ifcmphd.datapleafthenfmemhd.succ(tail::rem)elsefmemtail(hd.succ::rem))infmemptree[];;letreccardinal=function|[]->0|hd::tail->(cardinalhd.succ)+1+(cardinaltail);;(* linear time find *)letfindf=function|[]->raiseNot_found|hd::tail->iffhd.datathenhd.dataelseletrecmergefindtreerem=matchtreewith|[]->(matchremwith|[]->raiseNot_found|hd::rest->iffhd.datathenhd.dataelse(mergefindresthd.succ))|hd::tail->iffhd.datathenhd.dataelse(mergefindtail(hd.succ@rem))inmergefindhd.succtail;;(* linear time find *)letfind_optf=function|[]->None|hd::tail->iffhd.datathenSomehd.dataelseletrecmergefindtreerem=matchtreewith|[]->(matchremwith|[]->None|hd::rest->iffhd.datathenSomehd.dataelse(mergefindresthd.succ))|hd::tail->iffhd.datathenSomehd.dataelse(mergefindtail(hd.succ@rem))inmergefindhd.succtail;;(* count all duplicate occurences of pleaf *)letdupcountpleaftree=letrecfdupstreeacc=matchstreewith|[]->acc|hd::tl->ifequalhd.datapleafthenfduptl@@fduphd.succ(acc+1)else(fdup[@tailcall])tlaccinfduptree0;;(* insert with partial tolerance for duplicates, insertion order may not hold
when extracting. Especially if you insert AFTER an extract_min operation
however it should be fine if inserts happen at once before extracts
or in the case that updates are consistent with older inserted heap
entries and not creating Heap violations e.g. hill | basin inconsistency
TODO: Make list head the min pointer (along with consolidate) ??
*)letinsert?(cmp=minify)pleaftree=letrecpinsertpleafdupc=function|[]->[(instancepleafdupc)]|(hd::tail)->ifequalhd.datapleafthenletdupc'=dupc+1in{hdwithsucc=(pinsertpleaf(dupc')hd.succ)}::tailelseifcmphd.datapleafthen{hdwithsucc=(pinsertpleafdupchd.succ)}::tailelsehd::(pinsertpleafdupctail)inpinsertpleaf0tree;;(* insert with high tolerance for duplicates and try to preserve the insertion order
as much as possible - requires counting all duplicates ahead *)letdupinsert?(cmp=minify)pleaftree=(* TODO: By using a mutable ref, we can perhaps reference the duplicate
value and emplace it in new duplicate instances *)letdc=(dupcountpleaftree)+1inletrecdupinsertpleafdupc=function|[]->[(instancepleafdupc)]|(hd::tail)->ifequalhd.datapleafthen(* we could have duplicates with different priorities!
so watch out for hill or basin inconsistency *)ifcmphd.datapleafthen{hdwithsucc=(dupinsertpleaf(dupc)hd.succ)}::tailelse(* pleaf is fresh so we bubble down hd.data with its index *){data=pleaf;churn=0;index=dupc;succ=(dupinserthd.data(hd.index)hd.succ)}::(tail)elseifcmphd.datapleafthen{hdwithsucc=(dupinsertpleafdupchd.succ)}::tailelsehd::(dupinsertpleafdupctail)indupinsertpleafdctree;;letsingletonpleaf=[{data=pleaf;churn=0;index=0;succ=[]}];;(* insert like merge into an existing tree *)letrecmerge_node?(cmp=minify)pleaf=function|[]->[pleaf]|(hd::tail)ass->ifequalhd.datapleaf.datathenifhd.index<pleaf.indexthen{hdwithsucc=pleaf::(hd.succ@pleaf.succ)}::selse{pleafwithsucc=hd::(hd.succ@pleaf.succ)}::selseifcmphd.datapleaf.datathen{hdwithsucc=(merge_nodepleaf~cmp:cmphd.succ)}::tailelsehd::(merge_nodepleaf~cmp:cmptail);;(* remove all duplicate occurences of pleaf *)letdeduppleaftree=letrecfdupstreentreeacc=matchstreewith|[]->(ntree,acc)|hd::tl->ifequalhd.datapleafthenlet(ntree',acc')=fduphd.succntree(hd.data::acc)infduptlntree'acc'elsefduptl(hd::ntree)accinfduptree[][];;(* remove all duplicate occurences of pleaf in reverse insertion order *)letdedup_idxpleaftree=letrecfdupstreentreeacc=matchstreewith|[]->(ntree,acc)|hd::tl->ifequalhd.datapleafthenlet(ntree',acc')=fduphd.succntree((hd.data,hd.index)::acc)infduptlntree'acc'elsefduptl(hd::ntree)accinfduptree[][];;(* merge two tree elements *)letmerge?(cmp=minify)treetrunk=(* duplicate allowed to exit in successor *)ifequaltree.datatrunk.datathen(* try and preserve the insertion order *)iftree.index>trunk.indexthen{trunkwithsucc=tree::trunk.succ}else{treewithsucc=trunk::tree.succ}(* bubbling elements to maintain heap properties *)elseifcmptree.datatrunk.datathen(* bubble down the tree *){treewithsucc=(merge_node~cmp:cmptrunktree.succ)}else(* bubble up the trunk *){trunkwithsucc=(merge_node~cmp:cmptreetrunk.succ)};;exceptionEmptyletdegreetree=List.lengthtree.succ;;(** inorder traverse the heap, elements will likely be out of order
NB: In the rem, we use list of list rather than flat plain list to
speed up the process
*)letcollapse=function|[]->[]|{succ=child;data=pleaf;_}::tail->letrecfcollapsetreeremacc=matchtreewith|[]->(* reduce the number of `List.concat` operations by
'batching' lists - this works really good for dense heaps
*)(matchremwith|[]->acc|branch::left->(matchbranchwith|[]->(matchleftwith|[]->acc|l::r->fcollapselracc)|{succ=child'';data=pleaf'';_}::tl''->fcollapsechild''([tl'']@left)(pleaf''::acc)))|{succ=child';data=pleaf';_}::tl'->fcollapsechild'(tl'::rem)(pleaf'::acc)infcollapsechild([tail])([pleaf]);;(* straddles the root elts for the min *)letrecpeek?(cmp=minify)=function|[]->raiseEmpty|hd::tail->matchtailwith|[]->hd|fllw::rest->ifcmphd.datafllw.datathenpeek~cmp:cmp(hd::rest)elsepeek~cmp:cmp(fllw::rest);;letrecpeek_opt?(cmp=minify)=function|[]->None|hd::tail->matchtailwith|[]->Somehd|fllw::rest->ifcmphd.datafllw.datathenpeek_opt~cmp:cmp(hd::rest)elsepeek_opt~cmp:cmp(fllw::rest);;(* joins subtrees of equal degrees
The ntree is added back into the hashtable to be
rejoined with the one there if it existed.
this creates the binomial tree situation
*)letconsolidate?(cmp=minify)trees=(* push all nodes by degree *)letreccascaderejoin=letleftover=List.fold_left(funacceltree->letdeg=degreeeltreeinmatchHashtbl.find_opttbldegwith|Sometree->(* merge and rejoin *)letntree=merge~cmp:cmptreeeltreeinlet_=Hashtbl.removetbldeginntree::acc|None->let_=Hashtbl.addtbldegeltreeinacc)[]rejoininmatchleftoverwith|[]->()|_::_->cascadeleftoverinlet_=cascadetreesinletfin=List.of_seq@@Hashtbl.to_seq_valuestblinlet_=Hashtbl.cleartblinfin;;(*
TODO: Because we are traversing the root elements instead of memoizing the
min|max, this is actuall o(len(root elts)) and not o1, fix it!
*)letextract?(cmp=minify)=function|[]->raiseEmpty|hd::tail->(* straddle the root elts for the elt most true for cmp *)letrecsplithdtlacc=matchtlwith|[]->(hd,acc)|fllw::rest->ifcmphd.datafllw.datathensplithdrest(fllw::acc)elsesplitfllwrest(hd::acc)in(* consolidate all its successors to the root list *)let(it,rem)=splithdtail[]in(it.data,consolidate~cmp:cmp(rem@it.succ));;letextract_opt?(cmp=minify)=function|[]->None|head::tail->(* straddle the root elts for the elt most true for cmp *)letrecsplithdtlacc=matchtlwith|[]->(hd,acc)|fllw::rest->ifcmphd.datafllw.datathensplithdrest(fllw::acc)elsesplitfllwrest(hd::acc)in(* add all its successors to the root list
try and keep the number of root trees to a minimum
by joining same degreee nodes (consolidate) *)let(it,rem)=splitheadtail[]inSome(it.data,consolidate~cmp:cmp(rem@it.succ));;(* extract_all should yield a sorted list *)letrecextract_all?(cmp=minify)tree=letsml,rem=extract~cmp:cmptreeinmatchremwith|[]->[sml]|rest->sml::extract_all~cmp:cmprest;;letof_list?(cmp=minify)els=List.fold_right(insert~cmp:cmp)elsempty;;letto_seq?(cmp=minify)tree=letrecauxl()=matchextract_opt~cmp:cmplwith|None->Seq.Nil|Some(hd,tail)->Seq.Cons(hd,(auxtail))in(auxtree);;letrecof_seq?(cmp=minify)tseq=matchtseq()with|Seq.Nil->[]|Seq.Cons(x1,seq)->beginmatchseq()with|Seq.Nil->[(instancex10)]|Seq.Cons(x2,seq)->insert~cmp:cmp(x1)@@insert~cmp:cmp(x2)@@of_seq~cmp:cmpseqend;;(* extract until a condition is true should yield a sorted list *)letextract_til?(cmp=minify)ftree=letsml,rem=extract~cmp:cmptreeiniffsmlthen[sml]elsematchremwith|[]->[sml]|rest->sml::extract_all~cmp:cmprest;;(* basically search until we find the old-entry and replace with a new one
we have to confirm with the parent whether the main property holds and
change otherwise by "cutting it out" to the root
As this can slow extract by making us end up with trees of large degree
but few successors we mark parents to have them also be cut off once a
threshold of succesors have been "updated" by decrease or incr to sort-of
balance out the nodes
This operation may end up introducing duplicates from the new entry as a
replacement - which creates a new challenge
In the presence of duplicates, we decrease|increase only the first item to be
found (local only approach) if you want to update all, you would need to
either call it multiple times until it raises empty, but likely you need
to choose a better structure and comparator for your Entry.t to better
disambiguatae entries
decrease-key does NOT trigger consolidation of the root list.
This is actually crucial for maintaining the O(1) amortized time complexity of decrease-key.
*)letupdate?(cmp=minify)newenthdtlparentleftover=letmaxbelopt=(peek_opt~cmp:cmphd.succ)inmatchmaxbeloptwith|Somemaxbel->letpar,son=(cmpparent.datanewent),(cmpmaxbel.datanewent)inifpar&¬sonthen(* true, false -> ok and consistent *)({hdwithdata=newent}::tl,leftover,true)else(* true, true -> hill inconsistency *)let_=(parent.churn<-parent.churn+1)in(tl,{data=newent;churn=0;succ=[];index=hd.index;}::hd.succ,true)|None->letpar=(cmpparent.datanewent)inifparthen(* true, false -> ok *)({hdwithdata=newent}::tl,leftover,true)else(* true, true -> hill *)let_=(parent.churn<-parent.churn+1)in(tl,{data=newent;churn=0;succ=[];index=hd.index}::hd.succ,true);;(* local only increase, duplicates not updated
*)letincrease?(cmp=minify)oldbggertree=matchtreewith|[]->raiseEmpty|_::_->letrecatparentparenttreeleftoverfound=matchtreewith|[]->tree,leftover,found|(hd::tl)->if(equalhd.dataold)thenif(oequalhd.databgger)=1thenfailwith"new value must be larger"elseupdate~cmp:cmpbggerhdtlparentleftoverelseletnt,lf,wasfound=atparenthdhd.succleftoverfoundinifwasfoundthenifhd.churn>(!churn_threshold)then(* many children have died, we die as well :-( *)let_=(parent.churn<-(parent.churn+1))in(tl,{hdwithsucc=nt}::lf,wasfound)else({hdwithsucc=nt}::tl,lf,wasfound)else(* the node may have moved to the root, straddle
the tail *)letnt,lf,rem=atparentparenttlleftoverfoundin(hd::nt,lf,rem)inletself={data=bgger;churn=0;succ=[];index=0;}inletntree,left,found=atparentselftree[]falseiniffoundthen(* reset root churn values *)let_=(List.iter(funn->n.churn<-0)left)inntree@leftelsefailwith"value not in heap";;(* local only priority decrease, duplicates not updated
this operation is uninformed of the former nodes priority and thus searches
the whole list
*)letdecrease?(cmp=minify)oldsmllertree=matchtreewith|[]->raiseEmpty|_::_->letrecatparentparenttreeleftoverfound=matchtreewith|[]->(tree,leftover,found)|(hd::tl)->(* found the element *)if(equalhd.dataold)then(*if (Entry.ocompare (Entry.bind hd.data) (newent)) = -1 then*)if(oequalhd.datasmller)=-1thenfailwith"new value must be smaller"elseupdate~cmp:cmpsmllerhdtlparentleftoverelseletnt,lf,wasfound=(atparenthdhd.succleftoverfound)in(* backtrack, check if we hit a churn threshold *)ifwasfoundthenifhd.churn>(!churn_threshold)then(* many children have died, we die as well :-( *)let_=(parent.churn<-(parent.churn+1))in(tl,{hdwithsucc=nt}::lf,wasfound)else({hdwithsucc=nt}::tl,lf,wasfound)else(* the node may have moved to the root, straddle
the tail *)letnt,lf,rem=atparentparenttlleftoverfoundin(hd::nt,lf,rem)in(* start with self as its own parent *)letself={data=smller;churn=0;succ=[];index=0;}inletntree,left,wasfound=atparentselftree[]falseinifwasfoundthen(* reset churn values *)let_=(List.iter(funn->n.churn<-0)left)inntree@leftelsefailwith"value not in heap";;end