如何将此函数转换为尾递归

时间:2016-12-15 14:14:37

标签: ocaml tail-recursion

let rec hf0 k n =
if n <= k then n
else
    let rec loop i =
        if i > k then 0 else hf0 k (n - i) + loop (i + 1)
    in
    loop 1

我不知道怎么做,因为那里有一些递归函数。

(感谢@Jeffrey Scofield代码)

2 个答案:

答案 0 :(得分:1)

您的代码不是OCaml,甚至完全无法理解。但我的猜测是你正在寻找这个功能:

let rec hf0 k n =
    if n <= k then n
    else
        let rec loop i =
            if i > k then 0 else hf0 k (n - i) + loop (i + 1)
        in
        loop 1

这是一个尾递归版本:

let hf k n =
    let prev = Array.make k 0 in
    let rec ihf m =
        let res =
            if m <= k then m else Array.fold_left (+) 0 prev
        in
        if m >= n then
            res
        else
            begin
            Array.blit prev 0 prev 1 (k - 1);
            prev.(0) <- res;
            ihf (m + 1)
            end
    in
    if n <= k then n
    else if k <= 1 then k
    else ihf 0

为简单起见,此代码假设k> = 0。

正如@kne所说,它的工作原理是将中间计算从堆栈移动到堆中。特别是,它跟踪数组prev中的先前值。

<强>更新

这个函数很容易转换,因为它是一个类似Fibonacci计算的简单推广。

但一般情况下,您可以使用continuation passing style将任何计算转换为尾递归。

答案 1 :(得分:0)

尾递归不是你最大的问题。将递归函数转换为尾递归函数通常用于将线性或近线性递归的内存使用从堆栈移动到堆(并且希望甚至减少过程中的内存使用)。这样做是因为后者不太可能溢出。

在你的情况下,我看不到堆栈溢出是一个很大的问题,因为更早的时候(对于更小的n-k)你将遇到极端的运行时间。原因是你的递归远非线性:它由k分支。

您想要应用的技术是动态编程。作为旁注(取决于实现),它还可以为您提供持续的堆栈使用。

[编辑] 在评论中,您指出您的问题是大学的。如果你坚持尾递归,有几种选择。

  1. 函数调用只能有一个尾部。因此,严格地说,尾递归只能应用于 last 递归调用,而不是所有递归调用。在这种情况下使用累加器很容易实现:使用非尾递归计算除一个项之外的所有项的总和,并使用最后一项的尾递归,通过累加器传递部分和。

  2. 通过执行递归函数,我们可以关联一个调用树。在您的情况下,树的高度为n-k,每个非叶子将有k个孩子。控制流然后模仿深度优先遍历该树。您可以在单个循环中模拟此深度优先行走。 (树中的位置,您需要跟踪它,与调用堆栈紧密对应。)然后,您可以使用尾递归实现此循环。

相关问题