ocaml graph遍历:如何保留被访问节点?

时间:2012-12-18 04:06:21

标签: ocaml

我正在尝试使用DFS遍历图表。

但是当我尝试将访问节点列表作为函数的参数传递时,我发现存在问题。

当我到达除了前一个节点之外没有连接节点的节点时,递归调用结束,有关被访问节点的信息消失,因此陷入无限循环......

除了使用命令式方法之外,有没有办法保留有关被访问节点的信息?

2 个答案:

答案 0 :(得分:3)

阐述杰弗里的答案,你有几种不同的风格。我在这里只给出了我尚未测试的片段,因此可能存在小错误或大错误。

  1. 您可以在任何地方使用副作用:

    module NodeSet = Set.Make(...)
    
    let traverse action graph_root =
      let visited = ref NodeSet.empty in
      let rec loop node =
        action node;
        visited := NodeSet.add node !visited;
        let handle child =
          if not (NodeSet.mem child !visited)
          then loop acc child in
        List.iter handle (children node)
      in loop graph_root
    

    "访问"将命令函数action应用于所有节点 图表。

  2. 您可以将受访节点存储在可变引用中,但可以存储在线程中 作为累加器acc而不是遍历的遍历状态 直接测序副作用。这将对应于使用 国家monad。

    let traverse action init_state graph_root =
      let visited = ref NodeSet.empty in
      let rec loop acc node =
        let acc = action acc node in
        visited := NodeSet.add node !visited;
        let handle acc child =
          if NodeSet.mem child !visited
          then acc
          else loop acc child in
        List.fold_left handle acc (children node)
      in loop init_state graph_root
    
  3. 您可以重复使用此状态传递逻辑来传递被访问者 节点信息。

    let traverse action init_state graph_root =
      let rec loop acc visited node =
        let acc = action acc node in
        let visited = NodeSet.add node visited in
        let handle (acc, visited) child =
          if NodeSet.mem child !visited
          then (acc, visited)
          else loop acc visited child in
        List.fold_left handle (acc, visited) (children node)
      in loop init_state NodeSet.empty graph_root
    
  4. 最后,您可以通过传递进行尾递归遍历 有关哪个节点应在第一个节点中接下来计算的信息 递归调用。这对应于一般的转变 继续传递样式,但具有特定于域的表示 延续(只是访问的节点)。

    let traverse action init_state graph_root =
      let rec loop acc visited = function
        | [] -> acc
        | node::to_visit ->
           if NodeSet.mem node visited then loop acc visited to_visit
           else begin
             let acc = action acc node in
             let visited = NodeSet.add node visited in
             let to_visit = children node @ to_visit in
             loop acc visited to_visit
           end
      in loop NodeSet.empty init_state [graph_root]
    

    Jeffrey评论说,通过此演示文稿,您可以更改 通过简单地改变to_visit的方式从DFS到BFS的遍历顺序 更新,将子节点添加到序列的末尾 而不是在开头(这需要一个队列结构 算法效率高。)

答案 1 :(得分:2)

一种看待这种情况的方法是你想要前进来尝试图中其他可能的节点,而不是返回(就像你要做的那样,遍历一棵树)。您可以使用参数来描述您访问过的节点以及您计划访问的节点。 to-visit参数最初只是第一个节点。每次到达新节点时,都会将任何未访问的相邻节点(通过查看访问节点集可以判断)添加到未访问的节点集,并以递归方式继续这种方式。那么,DFS和BFS之间的区别在于您订购要访问的节点列表的方式。

在函数式编程中,有很多次,而不是从函数返回,而是调用函数来做下一件事。 (这就是尾递归有时很重要的原因。)