有向グラフを強連結成分に分解するアルゴリズム

CormenのIntroduction to Algorithmsには,グラフGと有向辺を逆にしたG_revに対してそれぞれDFSする方法が紹介されている.これは,わかりやすいアルゴリズムではあるのだけれど,逆グラフを求める必要があるのが玉にキズである.

一方,SedgewickのAlgorithms in C++には,逆グラフを求めないで1-passのDFSで求める手法が紹介されていた.こちらは連結成分を管理するためのスタックを別途使う必要があるが,逆グラフを求める必要がないのでうれしい.

そこで,OCamlで実装してみた.

module type DirectedGraphType =
  sig
    type t
    type node
    val all_nodes : t -> node list
    val adjacent_nodes : t -> node -> node list
  end

module type S =
  sig
    type t
    type node
    val decompose_scc : t -> node list list
  end

module Make(Graph: DirectedGraphType) =
  struct
    type t = Graph.t
    type node = Graph.node

    (*
     * decompose_scc: t -> node list list
     * 
     * -- decompose to strongly connected components(SCC)
     * -- from directed graph
     * 
     * ref. Algorithms in C++ by R. Sedgewick
     *)
    let decompose_scc g =
      let stack = Stack.create() in
      let all_nodes = Graph.all_nodes g in
      let node_num = List.length all_nodes in
      let values = Hashtbl.create node_num in
      let index = ref 0 in
      let results = ref  in
      let rec visit n =
        incr index;
        let min = ref !index in
          Hashtbl.add values n !index;
          Stack.push n stack;
          List.iter (
            fun t ->
              let m =
                if Hashtbl.mem values t then Hashtbl.find values t else visit t
              in
                if m < !min then min := m
          ) (Graph.adjacent_nodes g n);
          if !min == Hashtbl.find values n then
            (let rec loop ns =
              let m = Stack.pop stack in
                Hashtbl.replace values m (node_num + 1);
                if m != n then loop (m::ns) else (m::ns)
             in
               results := (loop )::!results);
          !min
      in
        List.iter
          (fun n -> if not (Hashtbl.mem values n) then ignore (visit n))
          all_nodes;
        !results
  end

基本的には参考書通りの実装だが,汎用的に使いたいのでファンクタとして定義している.