如何在这个F#程序(递归树搜索)中避免堆栈溢出?
我有这样一个受歧视的联合树:如何在这个F#程序(递归树搜索)中避免堆栈溢出?,f#,F#,我有这样一个受歧视的联合树: type rbtree = | LeafB of int | LeafR of int | Node of int*rbtree*rbtree internal static a tail@13<a>(FSharpList<int> results, FSharpFunc<FSharpList<int>, a> continuation, Program.rbtree tree) { wh
type rbtree =
| LeafB of int
| LeafR of int
| Node of int*rbtree*rbtree
internal static a tail@13<a>(FSharpList<int> results, FSharpFunc<FSharpList<int>, a> continuation, Program.rbtree tree)
{
while (true)
{
Program.rbtree rbtree = tree;
if (rbtree is Program.rbtree.LeafR)
{
goto IL_34;
}
if (!(rbtree is Program.rbtree.Node))
{
break;
}
Program.rbtree.Node node = (Program.rbtree.Node)tree;
Program.rbtree rt = node.item3;
FSharpList<int> arg_5E_0 = results;
FSharpFunc<FSharpList<int>, a> arg_5C_0 = new Program<a>.tail@17-1(continuation, rt);
tree = node.item2;
continuation = arg_5C_0;
results = arg_5E_0;
}
Program.rbtree.LeafB leafB = (Program.rbtree.LeafB)tree;
int v = leafB.item;
return continuation.Invoke(FSharpList<int>.Cons(v, results));
IL_34:
return continuation.Invoke(results);
}
我要做的是搜索树中的每个LeafB,所以我使用了一个递归函数:
let rec searchB (tree:rbtree) : rbtree list =
match tree with
| LeafB(n) -> LeafB(n)::searchB tree
| LeafR(n) -> []
| Node(n,left,right) -> List.append (searchB left) (searchB right)
但当我尝试测试它时,我得到了堆栈溢出异常,我不知道如何修改它以使其正常工作 哦,我可能会想出一个解决办法:
let rec searchB (tree:rbtree) : rbtree list =
match tree with
| LeafB(n) -> LeafB(n)::[]
| LeafR(n) -> []
| Node(n,left,right) -> List.append (searchB left) (searchB right)
现在,当我尝试它时,它看起来工作正常 正如@kvb所说,您的更新版本不是真正的tail rec,并且可能会导致堆栈溢出 您可以使用continuations,本质上使用堆空间而不是堆栈空间
let searchB_ tree =
let rec tail results continuation tree =
match tree with
| LeafB v -> continuation (v::results)
| LeafR _ -> continuation results
| Node (_, lt, rt) -> tail results (fun leftResults -> tail leftResults continuation rt) lt
tail [] id tree |> List.rev
如果我们在ILSpy
中查看生成的代码,它基本上如下所示:
type rbtree =
| LeafB of int
| LeafR of int
| Node of int*rbtree*rbtree
internal static a tail@13<a>(FSharpList<int> results, FSharpFunc<FSharpList<int>, a> continuation, Program.rbtree tree)
{
while (true)
{
Program.rbtree rbtree = tree;
if (rbtree is Program.rbtree.LeafR)
{
goto IL_34;
}
if (!(rbtree is Program.rbtree.Node))
{
break;
}
Program.rbtree.Node node = (Program.rbtree.Node)tree;
Program.rbtree rt = node.item3;
FSharpList<int> arg_5E_0 = results;
FSharpFunc<FSharpList<int>, a> arg_5C_0 = new Program<a>.tail@17-1(continuation, rt);
tree = node.item2;
continuation = arg_5C_0;
results = arg_5E_0;
}
Program.rbtree.LeafB leafB = (Program.rbtree.LeafB)tree;
int v = leafB.item;
return continuation.Invoke(FSharpList<int>.Cons(v, results));
IL_34:
return continuation.Invoke(results);
}
我们在函数Operators.op_Append(Program.searchB(左),Program.searchB(右))的末尾看到递归调用代码>
因此,尾部递归函数分配continuations函数,而不是创建新的堆栈框架。我们仍然可以用完堆,但堆比堆栈多得多
演示stackoverflow的完整示例:
type rbtree =
| LeafB of int
| LeafR of int
| Node of int*rbtree*rbtree
let rec searchB tree =
match tree with
| LeafB(n) -> n::[]
| LeafR(n) -> []
| Node(n,left,right) -> List.append (searchB left) (searchB right)
let searchB_ tree =
let rec tail results continuation tree =
match tree with
| LeafB v -> continuation (v::results)
| LeafR _ -> continuation results
| Node (_, lt, rt) -> tail results (fun leftResults -> tail leftResults continuation rt) lt
tail [] id tree |> List.rev
let rec genTree n =
let rec loop i t =
if i > 0 then
loop (i - 1) (Node (i, t, LeafB i))
else
t
loop n (LeafB n)
[<EntryPoint>]
let main argv =
printfn "generate left leaning tree..."
let tree = genTree 100000
printfn "tail rec"
let s = searchB_ tree
printfn "rec"
let f = searchB tree
printfn "Is equal? %A" (f = s)
0
键入rbtree=
|整型叶B
|整型叶
|int*rbtree*rbtree的节点
让rec搜索B树=
匹配树
|LeafB(n)->n::[]
|利弗(n)->[]
|节点(n,左,右)->List.append(searchB左)(searchB右)
让我们来搜索b_u树=
让rec跟踪结果延续树=
匹配树
|LeafB v->继续(v::结果)
|leaver uu->继续结果
|节点(u,lt,rt)->尾部结果(有趣的leftResults->tail leftResults continuation rt)lt
tail[]id tree |>List.rev
让我们来讨论一下=
让rec循环i t=
如果i>0,那么
循环(i-1)(节点(i,t,b i))
其他的
T
循环n(LeafB n)
[]
让主argv=
printfn“生成左倾树…”
let tree=genTree 100000
打印fn“尾部记录”
设s=searchB_uu树
printfn“rec”
设f=searchB树
printfn“是否相等?%A”(f=s)
0
只要你的树不太深,这就行了;但是,请注意,对searchB
的递归调用将导致堆栈增长,因此对于非常深的树,仍然可能出现堆栈溢出。