Haskell深度优先搜索图形

时间:2017-12-21 23:11:21

标签: algorithm haskell graph functional-programming depth-first-search

现在,我正在努力实现Haskell的深度优先搜索。我的depthfirst算法给出了一个起始节点和一个图。这就是我到目前为止+图数据类型的定义。

data Graph a = G [a] (BRfun a)

使用:

type BRfun a = a -> [a]

目前的尝试:

depthFirst :: Eq a => a -> Graph a -> [a]
depthFirst a (G [a] sucs) = [a]

因此,如果节点列表中只有一个元素是我必须放在最终列表中的唯一元素(我认为应该是取消条件)。 但现在我正在努力创建一个递归算法来首先得到最深的节点。

2 个答案:

答案 0 :(得分:0)

对于像DFS和BFS这样的图形搜索,您需要保留您之前访问过的顶点列表。这样就可以检查你之前是否看过一个顶点,这样你就不会访问一个顶点两次(这也会处理周期,尽管它实际上无法检测周期是否存在)。

这是我的实施。 visited列表跟踪已访问的顶点。对于我们遇到的每个顶点,我们检查是否通过遍历列表来访问它。当我们"访问"一个顶点(即在else分支中),我们将顶点添加到列表中。访问列表通过在foldl中传递来保持最新。

在这种方法中,我们实际上可以劫持visited列表来记录深度优先顺序。由于我们在第一次看到顶点时会将顶点添加到列表中,因此visited列表位于 reverse 深度优先顺序中。所以我们只需在搜索完成后撤消它。

depthFirst source (G _ sucs) = reverse (search [] source)
  where
    search visited v =
      if v `elem` visited
      then visited -- already seen v, so skip it
      else foldl search (v:visited) (sucs v)

我建议您仔细阅读代码在小图表上的执行方式,以了解代码的工作原理及其正确性。例如,在源0中按照以下定义的图表进行尝试。

edges = [[1,2,3],[4],[5],[4,6],[5],[1],[4]]
g = G [0,1,2,3,4,5,6] (edges!!)

最后,请注意,此实现是正确的但效率非常低,为n个顶点和m个边的图形花费时间O(nm),因为我们遍历每个边缘的访问列表一次。在一个更有效的实现中,您可能希望保留两个数据结构,一个用于查找是否已访问顶点(例如哈希集或二进制搜索树),另一个用于查找深度优先排序

答案 1 :(得分:0)

我喝了太多酒,对我正在谈论的内容有点模糊,但这是我提出的解决方案。

depthFirst :: Eq a => a -> Graph a -> [a]
depthFirst root (G _nodes edges)
  = reverse $ go [] root
  where
    go seen x
      | x `elem` seen = seen
      | otherwise = foldl' go (x:seen) (edges x)

我在foldl'使用Data.List,因为我们想要从左到右遍历节点,这对foldr来说有些挑战。直接使用foldl而不使用'通常不是一个好主意,因为它会构建thunks,除非强制(强制正是foldl'所做的)。

因此,正如我在评论中所概述的那样,总体思路如下。在第一次机会下到树下,保持你沿途看到的节点列表。如果一个节点没有传出边缘,很酷,你就完成了。如果你已经看过一个给定的节点,保释,你不需要无限递归。

折叠从当前节点开始,前面是已经看过的节点列表(在开头,空列表)。然后,从左到右,它访问从当前节点直接可到达的每个节点。在每个“下一个”节点,它构建子树的反向深度优先顺序加上已经看过的节点。已经看到的节点被转移到每个“下一个”节点(从左到右的顺序)。如果没有可从当前节点到达的节点,则它只返回前面所有节点列表的当前节点。

看到的节点列表被反转,因为前缀为O(1),而追加为O(n)。更容易反转一次并获得复杂性O(n)而不是每次追加并获得大致O(n²)的复杂性(复杂性来自我的头脑,我不仅有点醉,所以应用盐宽松)

如果elem x seen,函数会返回到目前为止看到的所有节点的列表。它确保我们不会递归到我们已经访问过的节点,因此避免了循环图上的无限递归。

这是经典的深度优先搜索。它可以进行优化,优化的可能性相当明显(对于一个,elem x seen具有O(n)最坏情况的复杂性,而它可能已经O(log n)。随意改进代码。

作为最后一点建议,Graph的类型并不能保证节点是唯一的。更严格的实现如下所示:data Graph a = G (Set a) (BRfun a),其中Set来自Data.Set(或类似的东西)。鉴于列表中所述的定义,可能是重新标记所有节点的好主意,f.ex。 nodes' = zip [1..] nodes或类似的东西。