问题描述
我在实现 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} }(可能包括一些箭头,指向已在访问集中的 vertex
es)。
在每个递归步骤中,您将任何此类向外箭头跳过到您已经访问过的顶点,并且当您找到具有最小权重的未访问顶点时,将该边添加到您的 vertex
,将未访问的顶点添加到那些 mst
,并使用从新访问的顶点发出的任何箭头来扩充 visited
箭头集。
(在您的代码中,您outgoing
,尽管没有技术需要这样做,因为它会作为“已访问”检查的一部分被过滤掉。)
对 delete x
进行上述更改后,您的 mkGraph
似乎可以在 prim
、g1
和图表上正常工作:
g2