我尝试解决在 Haskell 中查找所有连接子图的问题。此处描述了使用的算法。从那篇论文中引用:
就像在每个路径算法中一样,有向前的步骤和向后的步骤。如果给定的连通子图可以通过添加边 k 来扩展,即如果边 k 还不是给定子图的一部分,如果 k 与给定子图的至少一条边相邻,并且如果添加下面给出的一些限制不禁止边 k 的。一旦给定的连接子图不能被进一步拉长,就会后退一步。在这种情况下,最后添加的边被从字符串中删除,它暂时被赋予“禁止”状态,并且通过从先前较长的字符串回溯而被禁止的任何其他边同时再次“允许”。相反,通过从比当前字符串短的字符串中删除而被禁止的边仍然被禁止,
要执行此算法,我将图形表示为边列表:
type Edge = (Int,Int)
type Graph = [Edge]
首先,我编写addEdge
了检查是否可以扩展图形的函数,如果不可能则返回Nothing
或Edge
扩展。
我有一个"parent"
图形和"extensible"
图形,所以我尝试找到一个且只有一个存在于"parent"
图形中的边,与"extensible"
图形相连,尚未包含在"extensible"
图形中,因此未包含在forbidden
集合中。
我在下面写了这个函数:
addEdge :: Graph -> Graph -> [Edge] -> Maybe Edge
addEdge !parent !extensible !forb = listToMaybe $ intersectBy (\ (i,j) (k,l) -> (i == k || i == l || j == k || j == l)) (parent \\ (extensible `union` forb)) extensible
这是工作!但是,正如我从分析整个程序中看到的那样,addEdge
它是最重的功能。我敢肯定,我的代码不是最优的。Leastways, intersectBy
找到所有可能解决方案的功能,但我只需要一个。有没有办法让这段代码更快?也许,不要使用标准列表,但是Set
from Data.Set
?这是第一个关注点。
主要递归函数ext
如下所示:
ext :: Graph -> [Graph] -> Maybe Graph -> [(Edge,Int)] -> Int -> [Graph]
ext !main !list !grow !forb !maxLength | isEnd == True = (filter (\g -> (length g /= 1)) list) ++ (group main)
| ((addEdge main workGraph forbEdges) == Nothing) || (length workGraph) >= maxLength = ext main list (Just workGraph) forbProcess maxLength
| otherwise = ext main ((addedEdge:workGraph):list) Nothing forb maxLength where
workGraph = if grow == Nothing then (head list) else (bite (fromJust grow)) -- [Edge] graph now proceeded
workGraphLength = length workGraph
addedEdge = fromJust $ addEdge'
addEdge' = addEdge main workGraph forbEdges
bite xz = if (length xz == 1) then (fromJust (addEdge main xz forbEdges)):[] else tail xz
forbProcess = (head workGraph,workGraphLength):(filter ((<=workGraphLength).snd) forb)
forbEdges = map fst forb -- convert from (Edge,Level) to [Edge]
isEnd = (grow /= Nothing) && (length (fromJust grow) == 1) && ((addEdge main (fromJust grow) forbEdges) == Nothing)
我在图表上测试我的程序
c60 = [(1,4),(1,3),(1,2),(2,6),(2,5),(3,10),(3,7),(4,24),(4,21),(5,8),(5,7),(6,28),(6,25),
(7,9),(8,11),(8,12),(9,16),(9,13),(10,20),(10,17),(11,14),(11,13),(12,28),(12,30),(13,15),
(14,43),(14,30),(15,44),(15,18),(16,18),(16,17),(17,19),(18,47),(19,48),(19,22),(20,22),(20,21),
(21,23),(22,31),(23,32),(23,26),(24,26),(24,25),(25,27),(26,35),(27,36),(27,29),(28,29),(29,39),
(30,40),(31,32),(31,33),(32,34),(33,50),(33,55),(34,37),(34,55),(35,36),(35,37),(36,38),(37,57),
(38,41),(38,57),(39,40),(39,41),(40,42),(41,59),(42,45),(42,59),(43,44),(43,45),(44,46),(45,51),
(46,49),(46,51),(47,48),(47,49),(48,50),(49,53),(50,53),(51,52),(52,60),(52,54),(53,54),(54,56),(55,56),(56,58),(57,58),(58,60),(59,60)] :: Graph
例如,查找长度从 1 到 7 的所有子图
length $ ext c60 [[(1,2)]] Nothing [] 7
>102332
问题是计算速度太慢。正如它在原始文章中指出的那样,程序已FORTRAN 77
在 150MHz 工作站上编写并启动,执行测试任务的速度至少比我在现代 i5 处理器上的代码快 30 倍。我不明白,为什么我的程序这么慢?有没有办法重构这段代码?或者最好的解决方案是将其移植到 C 上,并通过 FFI 将绑定写入 C 库?