123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269(**************************************************************************)(* *)(* Ocamlgraph: a generic graph library for OCaml *)(* Copyright (C) 2004-2010 *)(* Sylvain Conchon, Jean-Christophe Filliatre and Julien Signoles *)(* *)(* This software is free software; you can redistribute it and/or *)(* modify it under the terms of the GNU Library General Public *)(* License version 2.1, with the special exception on linking *)(* described in file LICENSE. *)(* *)(* This software is distributed in the hope that it will be useful, *)(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *)(* *)(**************************************************************************)moduletypeG=sigtypetvalis_directed:boolmoduleV:Sig.COMPARABLEmoduleE:Sig.EDGEwithtypevertex=V.tvaliter_edges_e:(E.t->unit)->t->unitend(** The following implements Hierholzer's algorithm.
It is sketched as follows:
1. make a round trip from a random vertex, by following random
edges until we get back to the starting point (it will, as we
first check that all vertices have even degrees).
2. if any vertex along this cycle still has outgoing edges, pick one
and make another round trip from it, and then join the two cycles
into a single one. Repeat step 2 until all edges are exhausted.
The implementation makes use of the following:
- A table, called `out` in the following, that maps each vertex to
outgoing edges not yet used in the Eulerian path.
- In order to achieve optimal complexity, paths are built as
doubly-linked lists, so that we can merge two cycles with a common
vertex in constant time. This is type `dll` below.
*)moduleMake(G:G)=structopenGletreve=E.create(E.dste)(E.labele)(E.srce)moduleH=Hashtbl.Make(V)typeout=E.tH.tH.tletadd_out_edgeoutxye=lets=tryH.findoutxwithNot_found->lets=H.create4inH.addoutxs;sinH.addsye(** compute the table of outgoing edges *)letsetupg:int*out=letnbe=ref0inletout=H.create16inletadde=incrnbe;letx=E.srceandy=E.dsteinadd_out_edgeoutxye;ifnotis_directed&¬(V.equalxy)thenadd_out_edgeoutyx(reve)initer_edges_eaddg;!nbe,outexceptionFoundofV.tletanyh=tryH.iter(funv_->raise(Foundv))h;assertfalsewithFoundv->v,H.findhvtypedll={mutableprev:dll;edge:E.t;mutablenext:dll}letremove_edgeoute=letremovehxy=lets=H.findhxinassert(H.memsy);H.removesy;ifH.lengths=0thenH.removehxinletv=E.srceandw=E.dsteinremoveoutvwletselfe=V.equal(E.srce)(E.dste)letremove_edgeedgese=remove_edgeedgese;ifnotis_directed&¬(selfe)thenremove_edgeedges(reve)letany_out_edgeoutv=assert(H.memoutv);lets=H.findoutvinassert(H.lengths>0);let_,e=anysinremove_edgeoute;e(** build an arbitrary cycle from vertex [start] *)letround_tripedgesstart=lete=any_out_edgeedgesstartinletrecpath={prev=path;edge=e;next=path}inletrectoure=letv=E.dste.edgeinifV.equalvstartthen(path.prev<-e;path)else(lete'={prev=e;edge=any_out_edgeedgesv;next=path}ine.next<-e';toure')intourpathletconnectee'=e.next<-e';e'.prev<-e(** build an Eulerian cycle from vertex [start] *)leteulerian_cycleoutstart=lettodos=H.create8in(* vertex on cycle with out edges -> cycle edge *)lettodoe=letv=E.srce.edgeinifH.memoutvthenH.replacetodosveelseH.removetodosvinletrecupdatestarte=todoe;ifnot(V.equal(E.dste.edge)start)thenupdatestarte.nextinletpath=round_tripoutstartinupdatestartpath;whileH.lengthtodos>0doletv,e=anytodosinH.removetodosv;assert(H.memoutv);lete'=round_tripoutvinupdateve';letp=e.previnassert(p.next==e);letp'=e'.previnassert(p'.next==e');connectpe';connectp'e;done;pathletlist_ofpath=letrecconvertacce=ife==paththenList.revaccelseconvert(e.edge::acc)e.nextinconvert[path.edge]path.nextletmem_edgeoutxy=tryH.mem(H.findoutx)ywithNot_found->falseletout_degreeoutx=tryH.length(H.findoutx)withNot_found->0letundirectedg=letnbe,out=setupginletodds=H.create2inletcheckvs=letd=H.lengthsinletd=ifH.memsvthend-1elsedinifdmod2=1thenH.addoddsv()inH.itercheckout;letn=H.lengthoddsinifn<>0&&n<>2theninvalid_arg"Eulerian.path (bad degrees)";letcycle=n=0inletpath=ifcyclethenifnbe=0then[]elseletv,_=anyoutinlist_of(eulerian_cycleoutv)else((* we have two vertices x and y with odd degrees *)letx,_=anyoddsinH.removeoddsx;lety,_=anyoddsinifmem_edgeoutxythen((* there is an edge x--y => it connects 1 or 2 Eulerian cycles *)letxy=H.find(H.findoutx)yinremove_edgeoutxy;matchout_degreeoutx,out_degreeoutywith|0,0->[xy]|_,0->revxy::list_of(eulerian_cycleoutx)|0,_->xy::list_of(eulerian_cycleouty)|_->letpy=eulerian_cycleoutyin(* caveat: the cycle from y may exhaust edges from x *)ifout_degreeoutx=0thenxy::list_ofpyelselist_of(eulerian_cycleoutx)@xy::list_ofpy(* a bit of a pity to use list concatenation here,
but this does not change the complexity *))else((* no edge x--y => add one, build a cycle, then remove it *)letdummy=E.label(snd(any(H.findoutx)))inletxy=E.createxdummyyinH.add(H.findoutx)yxy;H.add(H.findouty)x(revxy);letp=eulerian_cycleoutxinletrecfinde=(* lookup for x--y, to break the cycle there *)letv=E.srce.edgeandw=E.dste.edgeinifV.equalvx&&V.equalwy||V.equalvy&&V.equalwxtheneelsefinde.nextinletstart=findpinList.tl(list_ofstart)))in(* check that all edges have been consumed *)ifH.lengthout>0theninvalid_arg"Eulerian.path (not connected)";path,cycleletdirectedg=letdelta=H.create16in(* out - in *)letaddvd=H.replacedeltav(d+tryH.finddeltavwithNot_found->0)inletadde=add(E.srce)1;add(E.dste)(-1)initer_edges_eaddg;letstart=refNoneandfinish=refNoneinletcheckv=function|1when!start=None->start:=Somev|-1when!finish=None->finish:=Somev|0->()|_->invalid_arg"Eulerian.path (bad degrees)"inH.itercheckdelta;letnbe,out=setupginletpath,cycle=match!start,!finishwith|None,Nonewhennbe=0->[],true|None,None->letv,_=anyoutinlist_of(eulerian_cycleoutv),true|Somes,Somef->(* add one edge f->s, build a cycle, then remove it
note: there may be already an edge f->s
if so, we are adding *a second one* and we are careful
about removing this one, not the other *)letdummy=E.label(snd(any(H.findouts)))inletfs=E.createfdummysinadd_out_edgeoutfsfs;letp=eulerian_cycleoutsinletrecfinde=(* lookup for f->s, to break the cycle there *)ife.edge==fstheneelsefinde.nextinletstart=findpinList.tl(list_ofstart),false|Some_,None|None,Some_->assertfalse(* since the sum of all deltas is zero *)in(* check that all edges have been consumed *)ifH.lengthout>0theninvalid_arg"Eulerian.path (not connected)";path,cycleletpath=ifis_directedthendirectedelseundirectedletcycleg=letp,c=pathginifnotctheninvalid_arg"Eulerian.cycle";pend