如何在 Haskell 中编写 MST 算法(Prim 或 Kruskal)?

Posted

技术标签:

【中文标题】如何在 Haskell 中编写 MST 算法(Prim 或 Kruskal)?【英文标题】:How can I write a MST algorithm (Prim or Kruskal) in Haskell? 【发布时间】:2011-05-16 11:10:39 【问题描述】:

我可以编写 Prim 和 Kruskal 的算法以在 C++ 或 Java 中找到最小生成树,但我想知道如何在 Haskell 中用 O(mlogm) 或 O(mlogn) 实现它们(纯函数程序更好) .非常感谢。

【问题讨论】:

你能解释一下,你的实施有什么问题吗?如果我们知道您的问题,就更容易为您提供帮助。 问题主要是数组问题。 Prim需要一个数组来记录节点是否被选中,Kruskal需要Union Find Set。修改数组成本很高。 我认为一棵树可能更好,当我将它用作 prims 中的标志数组时,它不会对复杂性产生影响。谢谢。 :) @is7s Iment ST 与状态。抱歉不准确。 【参考方案1】:

正如 svenningsson 所建议的,priority search queue 非常适合 Kruskal 和 Prim 的(至少作者在他的 paper 中声明了它。)Kruskal 的问题是它要求你有一个 O(log n) @987654323 @。 here 描述了具有纯函数式接口的联合查找数据结构,但它在内部使用可变状态,并且可能无法实现纯函数式实现,事实上,有几个问题不知道有效的纯函数式解决方案,如在this 相关的 SO 问题中讨论。

一个非纯的替代方案是在 ST monad 中实现 union-find 算法。在 Hackage 上搜索发现 equivalence 包适合我们的需要。以下是使用 equivalence 包中的 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)) 时间内运行的权重函数,这可以改进为 O(1 ) 时间通过使用数组或仅跟踪输入列表中的权重,但这并不是算法的真正部分。

【讨论】:

似乎存在一个有效的持久实现:www.lri.fr/~filliatr/ftp/publis/puf-wml07.ps @Landei,看来,一旦我对此进行了一些研究,我会更新我的答案。【参考方案2】:

这是一个粗略的 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/

【讨论】:

【参考方案3】:

我认为优先搜索队列是您正在寻找的。正如 Ralf Hinze 在a paper 中演示的那样,它可以用函数式语言最佳地实现。看来这篇论文只能通过 acm 的图书馆付费获得。

【讨论】:

这篇文章可以在这里找到:(portal.acm.org/…

以上是关于如何在 Haskell 中编写 MST 算法(Prim 或 Kruskal)?的主要内容,如果未能解决你的问题,请参考以下文章

在 Haskell 中优化基数排序

如何在纯 Haskell 中编写简单的实时游戏循环?

Haskell中的模块化算法

惯用的 Haskell 中如何实现动态规划算法?

为啥在 Prim 的 MST 算法中得到最小顶点?

在Haskell中使用OpenGL渲染PNG图像