我试图实现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)
所以我不认为它是算法中的一个错误,我们拥有相同的树结构,只是一棵更大的树正在导致问题。它看起来对我们的延续写得很好。我们已经明确输入了代码。并在所有情况下所有通话都以延续结束...
我们正在寻找专家意见!!!谢谢 !!!
嗯,是的,你没有做尾巴呼叫,所以它不能进行尾部呼叫优化。你的代码对于很多可变状态是非常必要的,这对于以功能方式工作通常很困难。 – Luaan
@Luaan。对不起,我是编程的初学者......你的意思是它没有(半)明显地将给定的递归代码转换为尾递归? –
我很欣赏任何想法如何使代码更多的功能与较少的可变变量,放弃全局变量等... –