在拓扑排序算法中,我们执行DFS并在遇到它们时将节点推送到链表。我可以想到在功能上执行此操作的唯一方法是将列表作为参数传递给调用,这是丑陋且非常低效的(即,它显示在大O中,因为复制列表是O(n)。如何在Haskell中以惯用方式执行此操作?
答案 0 :(得分:9)
fgl paper讨论了这个确切的问题,作为如何以函数式方式进行图算法的第一个例子:
dfs :: [Node] -> Graph a b -> [Node] dfs [] g = [] dfs (v:vs) (c &v g) = v:dfs (suc c++vs) g dfs (v:vs) g = dfs vs g
[编者注:本文前面介绍的符号
c &v g
是图表上的一种模式匹配":c &v g
匹配包含顶点的图表v
,将c
绑定到一个上下文,该上下文将边缘输入和输出顶点,g
绑定到该节点及其所有边缘都被删除。在Haskell库中,这是通过函数match
实现的。]该算法的工作原理如下。如果没有剩余的节点要访问(第一种情况),
dfs
会停止而不返回任何节点。相反,如果仍有必须访问的节点,dfs
会尝试在参数图中找到第一个节点(v
)的上下文。如果这是可能的(第二个等式),只要参数图中包含v
,v
就是结果节点列表中的下一个节点,并继续搜索剩余的图{在剩余的节点列表g
之前访问v
的后继者{1}}。接班人放在所有其他节点之前的事实导致vs
支持深度搜索,而不是广度搜索。最后,如果dfs
无法匹配(最后一行),v
将继续使用剩余的节点列表dfs
进行搜索。请注意,只有在图表中不包含vs
时才会出现最后一种情况,否则第二个等式中的模式将匹配。
由于复制粘贴工作不正常,我不得不转录此文本;任何拼写错误几乎都是我的,而不是Martin Erwig的。
如您所知,剩下要访问的节点列表作为参数传入。但是,你声称效率低下似乎对我不利;构建新列表v
的成本是O(suc c++vs
) - 但是您必须至少访问所有这些节点才能完成深度优先搜索,因此渐近成本是不可避免的。
上面链接的全文花了很多时间讨论渐近和效率,并且(在我看来)也很有启发性,所以我鼓励你给它一个阅读。