OCaml中的尾递归

时间:2016-04-11 18:20:48

标签: recursion ocaml tail-recursion

我正在尝试使用Tail递归在OCaml中实现合并功能,但是我遇到了尴尬的结果。任何人都可以帮我解决这个问题。提前致谢。

let rec merge_helper l1 l2 accum = 
match l1 with
[] -> l2@accum
| hd::tl -> (match l2 with
    [] -> l1@accum
    |x::xs -> merge_helper tl xs l1@l2@accum);;
let merge l1 l2 = merge_helper l1 l2 [];;

merge [1;2;4] [3;4;5];;
- : int list = [4; 5; 2; 4; 4; 5; 1; 2; 4; 3; 4; 5]

1 个答案:

答案 0 :(得分:3)

首先,您的实现不会在常量堆栈空间中运行。 xs @ ys操作不是尾递归的,并且会进行List.length xs次调用(因此使用这个堆栈帧数)。此外,merge函数通常会保留排序。所以你需要有一个比较函数,它将比较列表的元素。这并不是绝对清楚,您对merge函数的期望是什么,以及为什么您将结果归类为奇怪。对我来说,结果与代码匹配。对我来说非常奇怪的是,虽然您正在解构l1l2,但您没有使用解构结果并添加了整个列表l1l2到累加器。

方法应如下所示,从第一个列表中取一个元素,将此元素添加到累加器,然后切换列表。因此该算法的归纳步骤如下:

 let rec loop acc xs ys = match xs with
   ...
   | x::xs -> loop (x::acc) ys xs

但是如果你想合并两个保留顺序的排序列表,那么你需要在每一步中取两个列表中的最小元素。

let merge cmp xs ys = 
    let rec loop xs ys zs = match xs,ys with
      | [],ss|ss,[] -> List.rev_append zs ss
      | x :: txs, y :: tys -> 
         if cmp x y <= 0 
         then loop txs ys (x::zs)
         else loop xs tys (y::zs) in
    loop xs ys []

在归纳步骤中,我们采用较小的元素,并继续使用两个列表:较小元素的所有者的尾部(因为它被移动到累加器中),第二个列表被完全取得(因为没有积累任何东西)。由于我们在前面添加元素,它们将按相反的顺序排列,因此我们需要一些东西来反转结果(尾部递归的通常权衡)。基本情况允许我们在一个或另一个列表更短的情况下使我们的算法短路,并且我们不再需要将它们逐个进行比较,并且可以将更长列表的其余部分附加到累加器zs。我们使用List.rev_append将列表的剩余尾部附加到累加器。此函数将第一个列表的反转版本添加到第二个列表。