A while ago I wrote about the Bron - kerbosch
algorithm
giving a brain dead implementation based on simple lists. Of course
since the amount of maximal cliques can be exponential in the size of
graph, my previous implementation is useless on big graphs (say more
then 80 nodes and 150 edges). It just takes forever.
Now I give a more efficient version of the algorithm that uses a
simple lazy data structure to hold solution. Each element of the data
structure is a set of nodes. The first part is the definition of a
lazy list and a couple of ocamlgraph data structures. Pretty standard :
module PkgV = struct
type t = int
let compare = Pervasives.compare
let hash i = i
let equal = (=)
end
module UG = Graph.Imperative.Graph.Concrete(PkgV)
module N = Graph.Oper.Neighbourhood(UG)
module O = Graph.Oper.Make(Graph.Builder.I(UG))
module S = N.Vertex_Set
module RG = Graph.Rand.I(UG)
type 'a llist = 'a cell Lazy.t
and 'a cell = LList of 'a * 'a llist | Empty
exception LListEmpty
let empty = lazy(Empty)
let push e l = lazy(LList(e,l))
let hd s =
match Lazy.force s with
| LList (hd, _) -> hd
| Empty -> raise LListEmpty
let rec append s1 s2 =
lazy begin
match Lazy.force s1 with
| LList (hd, tl) -> LList (hd, append tl s2)
| Empty -> Lazy.force s2
end
let rec iter f s =
begin
match Lazy.force s with
| LList (hd, tl) -> f hd ; iter f tl
| Empty -> ()
end
let rec map f s =
lazy begin
match Lazy.force s with
| LList (hd, tl) -> LList (f hd, map f tl)
| Empty -> Empty
end
let rec flatten ss =
lazy begin
match Lazy.force ss with
| Empty -> Empty
| LList (hd, tl) ->
match Lazy.force hd with
| LList (hd2, tl2) -> LList (hd2, flatten (lazy (LList (tl2, tl))))
| Empty -> Lazy.force (flatten tl)
end
This second part is the algorithm itself. The fold function creates a
lazy list of sets of vertex to be check by the algorithm. The body of
the function each time choose a new pivot and invoke the bronKerbosch2
function on each of them in turn. When we finally manage to build a
clique we return it (push r empty
) embedded in a list. The
flatten (map f m) takes care of unwinding the results in a flat lazy list.
let rec bronKerbosch2 gr r p x =
let n v = N.set_from_vertex gr v in
let rec fold acc (p,x,s) =
if S.is_empty s then acc
else
let v = S.choose s in
let rest = S.remove v s in
let r' = S.union r (S.singleton v) in
let p' = S.inter p (n v) in
let x' = S.inter x (n v) in
let acc' = (push (r',p',x') acc) in
fold acc' (S.remove v p, S.add v x, rest)
in
if (S.is_empty p) && (S.is_empty x) then (push r empty)
else
let s = S.union p x in
let u = S.choose s in
let l = fold empty (p,x,S.diff p (n u)) in
flatten (map (fun (r',p',x') -> bronKerbosch2 gr r' p' x') l)
;;
let max_independent_sets gr =
let cgr = O.complement gr in
let r = S.empty in
let p = UG.fold_vertex S.add cgr S.empty in
let x = S.empty in
bronKerbosch2 cgr r p x
;;
To test it we just create a random graph and we iterates thought the
results. Noticed that if the algorithm was not lazy, for big graphs,
the iteration will get stuck waiting for the computation to be over.
let main () =
let v = int_of_string Sys.argv.(1) in
let e = int_of_string Sys.argv.(2) in
let gr = RG.graph ~loops:true ~v:v ~e:e () in
let mis = max_independent_sets gr in
let i = ref 0 in
iter (fun s ->
Printf.printf "%d -> %s\n" !i (String.concat " , " (List.map string_of_int (S.elements s)));
incr i
) mis
;;
main ();;
UPDATE
Following the lead from the comment below, I modified my lazy
algorithm to use a smarter pivot selection strategy. On a random graph
of 50 vertex and 80 edges I get almost 50% speed up ! This is great !
$./mis.native 50 80
--------------------------
Timer Mis classic. Total time: 15.035211.
Timer Mis pivot selection. Total time: 8.708919.
--------------------------
This is the code :
let choose gr cands s =
let n v = N.set_from_vertex gr v in
fst (
S.fold (fun u (pivot, pivot_score) ->
let score = S.cardinal (S.inter (n u) cands) in
if score > pivot_score then (u, score)
else (pivot, pivot_score)
) s (-1, -1)
)
let rec bronKerbosch_pivot gr clique cands nots =
let n v = N.set_from_vertex gr v in
let rec fold acc (cands,nots,s) =
if S.is_empty s then acc
else
let pivot = choose gr cands s in
let rest = S.remove pivot s in
let clique' = S.add pivot clique in
let cands' = S.inter cands (n pivot) in
let nots' = S.inter nots (n pivot) in
let acc' = (push (clique',cands',nots') acc) in
fold acc' (S.remove pivot cands, S.add pivot nots, rest)
in
if (S.is_empty cands) && (S.is_empty nots) then (push clique empty)
else
let s = S.union cands nots in
let pivot = choose gr cands s in
let rest = S.diff cands (n pivot) in
let l = fold empty (S.remove pivot cands,S.add pivot nots,rest) in
flatten (map (fun (clique',cands',nots') -> bronKerbosch_pivot gr clique' cands' nots') l)
let max_independent_sets_pivot gr =
let cgr = O.complement gr in
let clique = S.empty in
let cands = UG.fold_vertex S.add cgr S.empty in
let nots = S.empty in
bronKerbosch_pivot cgr clique cands nots