这是我的代码,当我输入一个非常大的数字时,我得到堆栈溢出错误有人知道为什么吗?当我输入一个非常大的数字我得到了这个错误,我不确定是什么导致它,它只是大数小的工作正常.....
//
// merge two sorted lists into one:
//
let rec merge L1 L2 =
if L1 = [] && L2 = [] then
[]
else if L1 = [] then
L2
else if L2 = [] then
L1
else if L1.Head <= L2.Head then
L1.Head :: merge L1.Tail L2
else
L2.Head :: merge L1 L2.Tail
//
// mergesort:
//
let rec mergesort L =
match L with
| [] -> []
| E::[] -> L
| _ ->
let mid = List.length L / 2
let (L1, L2) = List.splitAt mid L
merge (mergesort L1) (mergesort L2)
答案 0 :(得分:6)
在你的两个函数中你遇到了问题,你采取的最后一步不是递归调用,而是其他一些事情:
merge
中,::
操作mergesort
中,它是merge
所以你必须达到最后一点是递归调用的地步!
在你有多个递归调用的情况下,一种可能性是使用 continuations - 想法是传递一个函数,应该用当前步骤的结果调用,然后从那里继续计算。
这是mergesort
使用此技术的尾递归版本:
let mergesort xs =
let rec msort xs cont =
match xs with
| [] -> cont []
| [x] -> cont xs
| _ ->
let mid = List.length xs / 2
let (xs', xs'') = List.splitAt mid xs
msort xs' (fun ys' -> msort xs'' (fun ys'' -> cont (merge ys' ys'')))
msort xs id
正如你所看到的那样,这个想法并不难 - 而不是先调用两个递归路径,而是从一半开始,但增加了一个基本上说的延续:
我得到
mergesort xs'
的结果后,我会得到结果ys'
并继续mergesort
xs''
然后合并
当然第二步是以同样的方式完成(将merge
推入延续中)
第一个延续通常是您在最后一行中看到的身份;)
以下是merge
的类似内容:
let merge xs ys =
let rec mrg xs ys cont =
match (xs, ys) with
| ([], ys) -> cont ys
| (xs, []) -> cont xs
| (x::xs', y::ys') ->
if x < y
then mrg xs' ys (fun rs -> cont (x::rs))
else mrg xs ys' (fun rs -> cont (y::rs))
mrg xs ys id
那些当然会占用堆上的空间(可能更多) - 但这通常没问题 - 你的堆栈应该没问题;)
答案 1 :(得分:3)
每次递归调用都需要堆栈空间。 mergesort调用自身的次数越多,使用的堆栈就越多。
利用tail recursion避免堆栈溢出和递归函数。它只是意味着函数执行的最后一件事就是调用自身,调用被移除并转而成为跳转,从而节省了堆栈空间。
在您的情况下这很棘手,因为您必须两次调用mergesort。其中只有一个可以持续。解决方案是使用continuation。你只调用mergesort一次,但是传递一个函数来调用,这将第二次调用mergesort。
在互联网上搜索使用延续的合并排序的F#示例。