不正な状態遷移を見つけるアルゴリズム

http://d.hatena.ne.jp/a-san/20090623#p1
ガーベージコレクションでよく使われる、Mark&Sweep法がこの問題に使えるのに気づきました。
攻略法がわかれば、あとは実装して終わり。
マークをつける代わりに、たどったところは集合として扱いました。


言語は久しぶりにHaskellを使ってみました。

{-
    状態遷移で、到達しない状態を求める。
    ガーベージコレクションの Mark & Sweep法と同じアルゴリズムを使っている。
-}
import Data.Set as Set hiding (filter, map)

-- 状態遷移
-- 0 は開始状態。
-- A,B,Cは普通の状態遷移。
-- D,Hは遷移しない状態。
-- E,F,Gは孤立した状態遷移。
-- Iも孤立した状態。
transientList = [
    ('0','A'),
    ('A','B'),
    ('B','C'),
    ('C','B'),
    ('C','A'),
    ('D','B'),
    ('D','H'),
    ('H','D'),
    ('E','F'),
    ('F','G'),
    ('G','F'),
    ('G','E'),
    ('I','I')]


-- cur状態から次に遷移可能な状態をすべて列挙する。
-- 例えば、cur='C'ならば、['A','B']を返す。
nextState:: Eq a => [(a,a)] -> a -> [a]
nextState tranlist cur =
    snd $ unzip $ filter (\x -> fst x == cur) tranlist


-- cur状態から始めて、遷移可能な状態を返す。
visit:: Ord a => [(a,a)] -> a -> Set a -> Set a
visit translist cur visited =
    -- すでにその状態になったことがあるか?
    if member cur visited
        then visited
        -- 次の状態をすべて廻る。
        else unions $ map (\n -> visit translist n v) nexts
            where
                v = insert cur visited
                nexts = nextState translist cur

-- 状態遷移リストから、すべての状態を取得する。"0ABCDEFGHI"が返る。
allState:: Ord a => [(a,a)] -> Set a -> Set a
allState [] result = result
allState ((a,b):xs) result = 
    insert a $ insert b (allState xs result)

-- start状態から始めて、遷移しない状態を返す。
badState tranlist start =
    difference (allState tranlist empty) (visit tranlist start empty)

-- 問題を解く
main = badState transientList '0'