在 Haskell 中实现 Prim 的算法

问题描述

我在实现 prim 算法时遇到了困难,我的逻辑非常错误,这是我目前得到的:

import Data.Array
import Data.Function (on)
import Data.List (sortBy,delete)

type Vertex = Int
type Weight = Int
type Graph  = Array Vertex [(Vertex,Weight)]
type Bounds = (Vertex,Vertex)
type Edge   = (Vertex,Vertex,Weight)

g1 = mkGraph (1,4) [(1,2,1),(1,3,10),4,3),(2,4),(3,1)] -- 5
g2 = mkGraph (1,5) [(1,15),5),(4,5,20)] -- 34

mkGraph :: Bounds -> [Edge] -> Graph
mkGraph bounds edges = accumArray (\xs x -> x:xs) [] bounds [ (x,(y,w)) | (x,y,w) <- edges]

--[(1,[(4,1)]),4)]),[])]
prim :: Graph -> Vertex -> [(Vertex,Weight)]
prim graph start = prim' (sortBy (compare `on` snd) (graph ! start)) [start] []
    where
        prim' [] _ mst = mst
        prim' (x:xs) visited mst
            | elem (fst x) visited = prim' xs visited mst
            | otherwise            = prim' (delete x $ sortBy (compare `on` snd) ((graph ! (fst x)) ++ xs)) ((fst x):visited) (x:mst)

我的想法是,如果我把从顶点开始可能到达的每条边(假设为 1)选择最小值(在这种情况下是该列表中的第一个元素,因为它已排序),选择它元组中的第一个元素并将其用作索引,并使所有边均可从该顶点到达,同时添加从前一个顶点可到达的顶点。

在跟踪访问过的顶点时,问题是如果它到达最终顶点(它将是空的),那么它将停止添加边并仅使用已经添加的边。

但这也行不通,因为我一直在跟踪访问过的顶点的方式会跳过像 [(1,10) (2,1)] 这样的东西,因为它会标记顶点 3访问时。

解决方法

我认为问题在于您作为数组的 Graph 表示是隐式“有向”的,因此您需要获取输入的无向图并在两个方向上制表的边:: >

mkGraph :: Bounds -> [Edge] -> Graph
mkGraph bounds edges = accumArray (\xs x -> x:xs) [] bounds 
    [(x,(y,w)) | (x',y',w) <- edges,(x,y) <- [(x',y'),(y',x')]]

现在,递归的不变量是:

prim' outgoing visited mst

参数 outgoing(vertex,weight) 对的列表,所有有向、出射箭头从 visited 集合中的任何位置到其他一些 {{1} }(可能包括一些箭头,指向已在访问集中的 vertexes)。

在每个递归步骤中,您将任何此类向外箭头跳过到您已经访问过的顶点,并且当您找到具有最小权重的未访问顶点时,将该边添加到您的 vertex,将未访问的顶点添加到那些 mst,并使用从新访问的顶点发出的任何箭头来扩充 visited 箭头集。

(在您的代码中,您outgoing,尽管没有技术需要这样做,因为它会作为“已访问”检查的一部分被过滤掉。)

delete x 进行上述更改后,您的 mkGraph 似乎可以在 primg1 和图表上正常工作:

g2

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...