Prim と Kruskal の両方のアルゴリズムを記述して、C++ または Java で最小全域木を見つけることができますが、O(mlogm) または O(mlogn) を使用して Haskell でそれらを実装する方法を知りたいです (純粋な関数型プログラムの方が優れています)。どうもありがとう。
3 に答える
svenningsson が示唆しているように、 優先検索キューは Kruskal と Prim の両方に適しています (少なくとも著者は論文で宣言しています)。Kruskal の問題は、O(log n)ユニオン検索アルゴリズムが必要なことです。純粋に機能的なインターフェースを備えたユニオン検索データ構造についてはこちらで説明していますが、内部で変更可能な状態を使用しており、純粋に機能的な実装は不可能である可能性があり、実際には、効率的な純粋に機能的なソリューションが知られていないいくつかの問題があります。これに関連するSOの質問。
非純粋な代替手段は、ST モナドに共用体検索アルゴリズムを実装することです。Hackage で検索すると、同等のパッケージが私たちのニーズに合っていることがわかります。以下は、同等パッケージの Data.Equivalence.Monad を使用した Kruskal の実装です。
import Data.Equivalence.Monad
import Data.Graph as G
import Data.List(sortBy)
import Data.Map as M
import Control.Monad(filterM)
import Data.Ord(comparing)
run = runEquivM (const ()) (const $ const ())
kruskal weight graph = run $
filterM go (sortBy (comparing weight) theEdges)
where
theEdges = G.edges graph
go (u,v) = do
eq <- equivalent u v
if eq then return False else
equate u v >> return True
次のように使用できます。
fromL xs = fromJust . flip M.lookup (M.fromList xs)
testWeights = fromL [((1,2),1),((2,3),4),((3,4),5),((1,4),30),((1,3),4)]
testGraph = G.buildG (1,4) [(1,2),(2,3),(3,4),(1,4),(1,3)]
test = kruskal testWeights testGraph
実行中のテストは次のようになります。
[(1,2),(1,3),(3,4)]
実行時間は O(1) 時間で実行される重みに依存することに注意してください。ただしfromL
、O(log(n)) 時間で実行される重み関数を作成します。これは、配列または入力リストの重みを追跡するだけですが、実際にはアルゴリズムの一部ではありません。
これは大雑把な Kruskal の実装です。
import Data.List(sort)
import Data.Set (Set, member, fromList, insert, union)
data Edge a = Edge a a Double deriving Show
instance (Eq a) => Eq (Edge a) where
Edge x1 y1 z1 == Edge x2 y2 z2 = x1 == x2 && y1 == y2 && z1 == z2
instance Eq a => Ord (Edge a) where
(Edge _ _ x) `compare` (Edge _ _ y) = x `compare` y
kruskal :: Ord a => [Edge a] -> [Edge a]
kruskal = fst . foldl mst ([],[]) . sort
mst :: Ord a => ([Edge a],[Set a]) -> Edge a -> ([Edge a],[Set a])
mst (es, sets) e@(Edge p q _) = step $ extract sets where
step (rest, Nothing, Nothing) = (e : es, fromList [p,q] : rest)
step (rest, Just ps, Nothing) = (e : es, q `insert` ps : rest)
step (rest, Nothing, Just qs) = (e : es, p `insert` qs : rest)
step (rest, Just ps, Just qs) | ps == qs = (es, sets) --circle
| otherwise = (e : es, ps `union` qs : rest)
extract = foldr f ([], Nothing, Nothing) where
f s (list, setp, setq) =
let list' = if member p s || member q s then list else s:list
setp' = if member p s then Just s else setp
setq' = if member q s then Just s else setq
in (list', setp', setq')
最初のステップはエッジのソートです。これは O(n log n) です。問題は、抽出関数で頂点セットのより高速なルックアップを見つけることです。これに対するより速い解決策を見つけることができませんでした。誰かがアイデアを持っているかもしれません...
[アップデート]
Scala の実装では、(できれば) パフォーマンスを向上させるためにマップのようなデータ構造を使用しましたが、残念ながら、可変セットを使用しており、これを Haskell に変換する方法がわかりません。コードは私のブログにあります (申し訳ありませんが、説明はドイツ語です): http://dgronau.wordpress.com/2010/11/28/nochmal-kruskal/
探しているのは優先検索キューだと思います。Ralf Hinze が論文で実証したように、関数型言語で最適に実装できます。この論文は、acm のライブラリからのみ有料で入手できるようです。