2012-12-18 42 views
1

我在试图用DFS遍历一个图。ocaml图遍历:如何保存访问节点?

但是,当我试图通过访问节点列表作为函数的参数,我发现有一个问题。

当我具备除了其前一个节点没有连接的节点,递归调用结束,有关访问节点消失,所以陷入无限循环的信息的节点达到...

是否有任何的方式来保持关于访问节点的信息,除了使用命令的方式?

+0

我不知道OCAML,但你可以传递一个包含访问节点的集合吗? – Bill

+0

我是ocaml的新手,但ocaml是面向值的语言,所以一旦变量的值被设置,除了使用命令式的方式,这在函数式编程中不是首选的方式,没有办法改变它的值。 – glast

+0

在Clojure(功能性)中,当你添加到一个集合时,你会得到一个新的集合。语言中有一些构念支持这个概念。 – Bill

回答

3

详细阐述Jeffrey的答案,你有几种不同的风格可用。我在这里只给出了我没有测试过的片段,所以可能会有小的或大的错误。

  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] 
    

    杰弗里的言论,与此演示文稿,可以在 遍历顺序从DFS通过简单地改变着to_visit 更新更改为BFS,添加子节点的序列的末端,而 比年初(这需要在算法上有效的队列结构为 )。

+0

,不应该过滤已经访问过的节点吗?像'let to_visit = children node @ to_visit |> List.remove_if(翻转NodeSet.mem visited)in ...' – unhammer

+0

well spotted @unhammer,谢谢。在访问前我已经更改了代码以检查是否已访问过,而不是在收集时间的儿童时过滤掉。如果我没有记错,仅在儿童列表时间进行过滤是不正确的(因为节点可能已经在to_visit列表中)。仅在visting-time进行检查效率相当低,因为'to_visit'中可能会有很多重复项,但是代码形状可以更好地适用于加权边缘应用程序,如Dijkstra,并不是所有的方法都是平等的。 – gasche

+0

“仅在儿童列表时间进行过滤(因为节点可能已经在to_visit列表中)” - 我不确定我是否理解;我评论中的代码片段过滤了儿童和to_visit的* concatenation *。当我测试它时,我无法让它失败,但是我不知道我是否可以证明它是正确的:-)你有反例吗? – unhammer

2

看看这个的一种方法是,你想要转发来尝试图中其他可能的节点,而不是返回(就像你会这样做,例如遍历一棵树)。您可以使用参数来描述您访问过的节点,但您计划访问的节点。访问参数最初只是第一个节点。每次到达新节点时,都会将所有未访问的相邻节点(通过查看访问节点集可以看出)添加到未访问节点集,并以递归方式继续。然后,DFS和BFS之间的区别就在于您对要访问的节点列表进行排序。

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