合并排序为f sharp

时间:2016-02-24 05:43:23

标签: f# mergesort

这是我的代码,当我输入一个非常大的数字时,我得到堆栈溢出错误有人知道为什么吗?当我输入一个非常大的数字我得到了这个错误,我不确定是什么导致它,它只是大数小的工作正常.....

//
// 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)

2 个答案:

答案 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#示例。