2

我试图实现Kosaraju的一个大图 算法作为转让的一部分[MOOC ALGO我斯坦福的Coursera]尾递归:堆栈溢出

https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm

当前的代码适用于一个小图,但我在运行时执行期间遇到堆栈溢出。

尽管已经阅读专家在F#网站上的相关章节,或其他可用的例子,所以,我还是不明白如何使用延续来解决这个问题

下面是通用的完整代码,但在执行DFSLoop1和递归函数DFSsub时,它已经失败了。我想,我不是做功能尾递归[因为指令

t<-t+1 
G.[n].finishingtime <- t 

?]

,但我不明白,我怎么能够正常实施的延续。

当仅考虑失败的部分时,DFSLoop1将作为我们将应用深度优先搜索的图形的参数。我们需要将完成时间记录为算法的一部分,以便在第二个DFS循环(DFSLoop2)中进入算法的第二部分[当然,我们在此之前是失败的]。

open System 
open System.Collections.Generic 
open System.IO 

let x = File.ReadAllLines "C:\Users\Fagui\Documents\GitHub\Learning Fsharp\Algo Stanford I\PA 4 - SCC.txt";; 
// let x = File.ReadAllLines "C:\Users\Fagui\Documents\GitHub\Learning Fsharp\Algo Stanford I\PA 4 - test1.txt";; 
// val x : string [] = 

let splitAtTab (text:string)= 
    text.Split [|'\t';' '|] 

let splitIntoKeyValue (A: int[]) = 
    (A.[0], A.[1]) 

let parseLine (line:string)= 
    line 
    |> splitAtTab 
    |> Array.filter (fun s -> not(s="")) 
    |> Array.map (fun s-> (int s)) 
    |> splitIntoKeyValue 

let y = 
    x |> Array.map parseLine 
//val it : (int * int) [] 

type Children = int[] 
type Node1 = 
    {children : Children ; 
     mutable finishingtime : int ; 
     mutable explored1 : bool ; 
     } 

type Node2 = 
    {children : Children ; 
     mutable leader : int ; 
     mutable explored2 : bool ; 
     } 

type DFSgraphcore = Dictionary<int,Children> 
let directgraphcore = new DFSgraphcore() 
let reversegraphcore = new DFSgraphcore() 

type DFSgraph1 = Dictionary<int,Node1> 
let reversegraph1 = new DFSgraph1() 

type DFSgraph2 = Dictionary<int,Node2> 
let directgraph2 = new DFSgraph2() 

let AddtoGraph (G:DFSgraphcore) (n,c) = 
    if not(G.ContainsKey n) then 
           let node = [|c|] 
           G.Add(n,node) 
          else 
           let c'= G.[n] 
           G.Remove(n) |> ignore 
           G.Add (n, Array.append c' [|c|]) 

let inline swaptuple (a,b) = (b,a) 
y|> Array.iter (AddtoGraph directgraphcore) 
y|> Array.map swaptuple |> Array.iter (AddtoGraph reversegraphcore) 

for i in directgraphcore.Keys do 
    if reversegraphcore.ContainsKey(i) then do 

       let node = {children = reversegraphcore.[i] ; 
          finishingtime = -1 ; 
          explored1 = false ; 
          } 
       reversegraph1.Add (i,node) 

     else         
       let node = {children = [||] ; 
          finishingtime = -1 ; 
          explored1 = false ; 
          } 
       reversegraph1.Add (i,node) 

directgraphcore.Clear |> ignore 
reversegraphcore.Clear |> ignore 

// for i in reversegraph1.Keys do printfn "%d %A" i reversegraph1.[i].children 
printfn "pause" 
Console.ReadKey() |> ignore 

let num_nodes = 
    directgraphcore |> Seq.length 


let DFSLoop1 (G:DFSgraph1) = 
    let mutable t = 0 
    let mutable s = -1 
    let mutable k = num_nodes 

    let rec DFSsub (G:DFSgraph1)(n:int) (cont:int->int) = 
    //how to make it tail recursive ??? 

      G.[n].explored1 <- true 
      // G.[n].leader <- s 
      for j in G.[n].children do 
         if not(G.[j].explored1) then DFSsub G j cont 
      t<-t+1 
      G.[n].finishingtime <- t 

    // end of DFSsub 

    for i in num_nodes .. -1 .. 1 do 
     printfn "%d" i 
     if not(G.[i].explored1) then do 
            s <- i 
            (DFSsub G i (fun s -> s)) |> ignore 
    // printfn "%d %d" i G.[i].finishingtime 

DFSLoop1 reversegraph1 

printfn "pause" 
Console.ReadKey() |> ignore 

for i in directgraphcore.Keys do 
    let node = {children = 
         directgraphcore.[i] 
         |> Array.map (fun k -> reversegraph1.[k].finishingtime) ; 
       leader = -1 ; 
       explored2= false ; 
       } 
    directgraph2.Add (reversegraph1.[i].finishingtime,node) 

let z = 0 

let DFSLoop2 (G:DFSgraph2) = 
    let mutable t = 0 
    let mutable s = -1 
    let mutable k = num_nodes 

    let rec DFSsub (G:DFSgraph2)(n:int) (cont:int->int) = 

      G.[n].explored2 <- true 
      G.[n].leader <- s 
      for j in G.[n].children do 
         if not(G.[j].explored2) then DFSsub G j cont 
      t<-t+1 
      // G.[n].finishingtime <- t 

    // end of DFSsub 

    for i in num_nodes .. -1 .. 1 do 
     if not(G.[i].explored2) then do 
            s <- i 
            (DFSsub G i (fun s -> s)) |> ignore 
     // printfn "%d %d" i G.[i].leader 

DFSLoop2 directgraph2 

printfn "pause" 
Console.ReadKey() |> ignore 


let table = [for i in directgraph2.Keys do yield directgraph2.[i].leader] 
let results = table |> Seq.countBy id |> Seq.map snd |> Seq.toList |> List.sort |> List.rev 
printfn "%A" results 

printfn "pause" 
Console.ReadKey() |> ignore 

下面是一个简单的图形示例的文本文件

1 4 
2 8 
3 6 
4 7 
5 2 
6 9 
7 1 
8 5 
8 6 
9 7 
9 3 

(这是造成溢出的一个是70Mo大周围900000节点)

EDIT

首先澄清几件事 这里是“伪代码”

输入:一个有向图G =(V,E),在邻接表中表示。假设顶点V被标记为 1,2,3,...。 。 。 ,n。 1.令Grev表示在所有弧的方向已经颠倒之后的图G. 2.在Grev上运行DFS-Loop子例程,按照给定顺序处理顶点,以获得每个顶点v∈V的结束时间f(v)。 3.在G上运行DFS-Loop子例程,按f(v)的降序处理顶点,为每个顶点v∈V分配一个领导者 。 4.G的强连通分量对应于共享公共领导者的G的顶点。 图2:我们的SCC算法的顶层。在第一个和第二个调用DFS-Loop的 中分别计算f值和领导(见下文)。

输入:有向图G =(V,E),在邻接列表表示中。 1.将全局变量t初始化为0. [这会跟踪已完全探索的顶点数量。] 2.将全局变量s初始化为NULL。 [这会跟踪从哪个顶点调用最后一个DFS调用。] 3.对于i = n downto 1: [在第一个调用中,顶点标记为1,2,...。 。 。 ,n任意。在第二次调用中,顶点标记为 它们的f(v) - 来自第一次调用的值。] (a)如果我尚未探索: i。设置s:= i ii。 DFS(G,i) 图3:DFS循环子程序。

输入:有向图G =(V,E),邻接列表表示,源顶点i∈V。 1.将我标记为已探索。 [在DFS循环调用的整个持续时间内仍然探索] 2.设置领导者(i):= s 3.对于每个弧(i,j)∈G: (a)如果j还没有探索: 我。 DFS(G,j) 4. t + + 5.设置f(i):= t 图4:DFS子程序。只需在第一次调用DFS循环期间计算f值,并且只需在第二次调用DFS循环期间计算领导者值。

编辑 我修改了代码,拥有一支经验丰富的程序员(一个利斯佩尔但谁在F#没有经验)的帮助下有所简化第一部分有更快速的例子,而不理会不相关的代码为这个讨论。

该代码只关注算法的一半,一次运行DFS以获得反转树的结束时间。

这是代码的第一部分,只是创建一个小例子 y是原始树。元组的第一个元素是父元素,第二个元素是子元素。但是,我们将与反向树来工作

open System 
open System.Collections.Generic 
open System.IO 

let x = File.ReadAllLines "C:\Users\Fagui\Documents\GitHub\Learning Fsharp\Algo Stanford I\PA 4 - SCC.txt";; 
// let x = File.ReadAllLines "C:\Users\Fagui\Documents\GitHub\Learning Fsharp\Algo Stanford I\PA 4 - test1.txt";; 
// val x : string [] = 

let splitAtTab (text:string)= 
    text.Split [|'\t';' '|] 

let splitIntoKeyValue (A: int[]) = 
    (A.[0], A.[1]) 

let parseLine (line:string)= 
    line 
    |> splitAtTab 
    |> Array.filter (fun s -> not(s="")) 
    |> Array.map (fun s-> (int s)) 
    |> splitIntoKeyValue 

// let y = 
// x |> Array.map parseLine 

//let y = 
// [|(1, 4); (2, 8); (3, 6); (4, 7); (5, 2); (6, 9); (7, 1); (8, 5); (8, 6); 
// (9, 7); (9, 3)|] 

// let y = Array.append [|(1,1);(1,2);(2,3);(3,1)|] [|for i in 4 .. 10000 do yield (i,4)|] 
let y = Array.append [|(1,1);(1,2);(2,3);(3,1)|] [|for i in 4 .. 99999 do yield (i,i+1)|] 



//val it : (int * int) [] 

type Children = int list 
type Node1 = 
    {children : Children ; 
     mutable finishingtime : int ; 
     mutable explored1 : bool ; 
     } 

type Node2 = 
    {children : Children ; 
     mutable leader : int ; 
     mutable explored2 : bool ; 
     } 

type DFSgraphcore = Dictionary<int,Children> 
let directgraphcore = new DFSgraphcore() 
let reversegraphcore = new DFSgraphcore() 

type DFSgraph1 = Dictionary<int,Node1> 
let reversegraph1 = new DFSgraph1() 

let AddtoGraph (G:DFSgraphcore) (n,c) = 
    if not(G.ContainsKey n) then 
           let node = [c] 
           G.Add(n,node) 
          else 
           let c'= G.[n] 
           G.Remove(n) |> ignore 
           G.Add (n, List.append c' [c]) 

let inline swaptuple (a,b) = (b,a) 
y|> Array.iter (AddtoGraph directgraphcore) 
y|> Array.map swaptuple |> Array.iter (AddtoGraph reversegraphcore) 

// définir reversegraph1 = ... with.... 
for i in reversegraphcore.Keys do 
    let node = {children = reversegraphcore.[i] ; 
          finishingtime = -1 ; 
          explored1 = false ; 
          } 
    reversegraph1.Add (i,node) 

for i in directgraphcore.Keys do 
    if not(reversegraphcore.ContainsKey(i)) then do         
       let node = {children = [] ; 
          finishingtime = -1 ; 
          explored1 = false ; 
          } 
       reversegraph1.Add (i,node) 

directgraphcore.Clear |> ignore 
reversegraphcore.Clear |> ignore 

// for i in reversegraph1.Keys do printfn "%d %A" i reversegraph1.[i].children 
printfn "pause" 
Console.ReadKey() |> ignore 

let num_nodes = 
    directgraphcore |> Seq.length 

所以基本上图是(1-> 2-> 3-> 1)::(4-> 5-> 6-> 7-> 8- > - > 99999-> 10000) 并且反转图是(1-> 3-> 2-> 1)::(10000-> 9999 - > ....-> 4)

这里是一个用直接的风格

//////////////////// main code is below /////////////////// 

let DFSLoop1 (G:DFSgraph1) = 
    let mutable t = 0 
    let mutable s = -1 

    let rec iter (n:int) (f:'a->unit) (list:'a list) : unit = 
     match list with 
      | [] -> (t <- t+1) ; (G.[n].finishingtime <- t) 
      | x::xs -> f x ; iter n f xs  
    let rec DFSsub (G:DFSgraph1) (n:int) : unit = 
      let my_f (j:int) : unit = if not(G.[j].explored1) then (DFSsub G j) 
      G.[n].explored1 <- true   
      iter n my_f G.[n].children 

    for i in num_nodes .. -1 .. 1 do 
     // printfn "%d" i 
     if not(G.[i].explored1) then do 
            s <- i 
            DFSsub G i               

     printfn "%d %d" i G.[i].finishingtime 

// End of DFSLoop1 


DFSLoop1 reversegraph1 

printfn "pause" 
Console.ReadKey() |> ignore 

它不是尾递归主要的代码,所以我们用的延续,这里是适应CPS风格相同的代码:

//////////////////// main code is below /////////////////// 
let DFSLoop1 (G:DFSgraph1) = 
    let mutable t = 0 
    let mutable s = -1 

    let rec iter_c (n:int) (f_c:'a->(unit->'r)->'r) (list:'a list) (cont: unit->'r) : 'r = 
     match list with 
      | [] -> (t <- t+1) ; (G.[n].finishingtime <- t) ; cont() 
      | x::xs -> f_c x (fun()-> iter_c n f_c xs cont) 
    let rec DFSsub (G:DFSgraph1) (n:int) (cont: unit->'r) : 'r= 
      let my_f_c (j:int)(cont:unit->'r):'r = if not(G.[j].explored1) then (DFSsub G j cont) else cont() 
      G.[n].explored1 <- true   
      iter_c n my_f_c G.[n].children cont 


    for i in maxnum_nodes .. -1 .. 1 do 
     // printfn "%d" i 
     if not(G.[i].explored1) then do 
            s <- i 
            DFSsub G i id               

     printfn "%d %d" i G.[i].finishingtime 


DFSLoop1 reversegraph1 
printfn "faré" 
printfn "pause" 
Console.ReadKey() |> ignore 

两个代码编译,并给出了小例子(那个在评论),或者我们使用的是同一棵树相同的结果,具有更小的尺寸(1000,而不是100000)

所以我不认为它是算法中的一个错误,我们拥有相同的树结构,只是一棵更大的树正在导致问题。它看起来对我们的延续写得很好。我们已经明确输入了代码。并在所有情况下所有通话都以延续结束...

我们正在寻找专家意见!!!谢谢 !!!

+0

嗯,是的,你没有做尾巴呼叫,所以它不能进行尾部呼叫优化。你的代码对于很多可变状态是非常必要的,这对于以功能方式工作通常很困难。 – Luaan

+0

@Luaan。对不起,我是编程的初学者......你的意思是它没有(半)明显地将给定的递归代码转换为尾递归? –

+0

我很欣赏任何想法如何使代码更多的功能与较少的可变变量,放弃全局变量等... –

回答

0

好的,所以上面给出的代码是正确的代码! 问题在于F#

这里的编译器是它从微软 http://blogs.msdn.com/b/fsharpteam/archive/2011/07/08/tail-calls-in-fsharp.aspx

基本上有些话,要小心的设置,在默认模式下,编译器可能不会自动使尾调用。为此,在VS2015中,转到解决方案资源管理器,右键单击鼠标并单击“属性”(滚动列表的最后一个元素) 然后在新窗口中,单击“Build”并勾选“产生尾调用”

这也是检查的编译器做了自己的工作考虑使用 程序Ildasm.exe

你可以找到整个算法中的源代码在我的github仓库拆卸

https://github.com/FaguiCurtain/Learning-Fsharp/blob/master/Algo%20Stanford/Algo%20Stanford/Kosaraju_cont.fs

从性能的角度来看,我不是很满意。代码在我的笔记本电脑上运行36秒。从论坛上与其他同行的MOOCers一起,C/C++/C#通常在5秒内执行,Java在10-15左右,Python在20-30s左右执行。 所以我的实现显然没有优化。我现在很高兴听到关于使技巧更快的技巧!谢谢 !!!!

5

我没有试着理解整个代码片断,因为它相当长,但是您肯定需要用使用continuation passing样式实现的迭代替换for循环。喜欢的东西:

let rec iterc f cont list = 
    match list with 
    | [] -> cont() 
    | x::xs -> f x (fun() -> iterc f cont xs) 

我不明白的cont目的在DFSub功能(这是从来没有所谓的,是吗?),但基于延续的版本看起来大致是这样的:

let rec DFSsub (G:DFSgraph2)(n:int) cont = 
    G.[n].explored2 <- true 
    G.[n].leader <- s 
    G.[n].children 
    |> iterc 
     (fun j cont -> if not(G.[j].explored2) then DFSsub G j cont else cont()) 
     (fun() -> t <- t + 1) 
+0

是for循环也导致溢出? –

+0

另外,如果let recercher f cont list中的参数是MUTABLE变量,它是否有意义,或者我们确实必须使用不可变类型来避免溢出? –

+0

嗨;我已经在一位朋友的帮助下更新了代码。他(我们)认为它的语义是正确的,但它仍然导致溢出......请你再看一下吗? –

2

充溢堆栈,当你通过几十万项的递归是不差,真的。很多编程语言实现会比这个短得多的递归窒息。你有严重的程序员问题 - 没有什么可羞愧的!

现在,如果您想执行比实现更深的递归操作,您需要转换算法,使其成为迭代和/或尾递归(两者是同构的 - 除了尾递归允许分散和模块化,而迭代是集中的和非模块化的)。您需要理解隐式存储在堆栈框架中的状态,即函数体中的变量跨越递归,并将它们明确地存储在FIFO队列中(一个复制堆栈的数据结构,并可作为链表简单实现)。然后,您可以将该已链接的帧变量链表作为参数传递给您的尾递归函数。

在更高级的情况下,您有许多尾递归函数,每个函数都有不同类型的框架,而不是简单的自递归,您可能需要为被通用的栈帧定义一些相互递归的数据类型,而不是使用名单。但我相信Kosaraju的算法只涉及自递归函数。 OK,