【问题标题】:What are the advantages of tail recursive compare to recursive [duplicate]与递归相比,尾递归有什么优势[重复]
【发布时间】:2019-06-02 17:03:21
【问题描述】:

所以尾递归意味着在主体中使用一个递归调用的递归函数。此外,它是最外层的,这意味着递归调用不在语句内部,而是在递归调用的参数上完成递归。 例如(这里我用的是OCaml),

递归:

let rec fact n =
if n=0 then 1
else n* fact (n-1);;

尾递归:

let fact n =
let rec helper (n,m)=
    if n=0 then m
    else helper(n-1,n*m)
in 
helper(n,1);;

我不明白为什么尾递归会比另一种更好?

还有一些关于 OCamel 的问题:我们必须担心压痕吗?因为当我在 tryOCamel 中尝试时,我们似乎没有这样做。

另外,我们可以为 OCaml 问题制作一个新标签吗? 请帮忙

【问题讨论】:

  • 尾递归不需要在返回的每一步都对结果进行乘法运算,只需返回原始被调用者即可。实际上,只有第一个调用是真正的函数调用,而其他调用将是 goto。需要在函数的结果上继续执行的递归需要返回以执行该步骤,并且如果递归足够深,通常会破坏堆栈。尾递归不必使用称为尾调用消除的技巧来增加堆栈。
  • OCaml 没有缩进块。您的屏幕截图显示语法错误,因为 ;; 终止了表达式,但 let rec funct= 不完整。此外,您似乎在某处有错字fuct 而不是funct。请复制粘贴您的输出,而不是放入屏幕截图,因为盲人无法轻松阅读它们。

标签: functional-programming ocaml


【解决方案1】:

尾递归不会引起与常规递归相关的堆栈深度问题。由于尾递归直接将结果返回给父级,因此在进行下一次递归调用时会丢弃堆栈帧。这允许更深(无限)的递归。递归调用中的每个内部步骤本质上都被新调用替换了。

在你的例子中:

let rec fact n =
    if n=0 then 1
    else n * fact (n-1);;

n * fact(n-1) 中的第一个 n 必须为每次调用迭代存储;所以它可以在堆栈展开时使用(n * ...)。

您的第二个示例将 n * 当前结果 (m) 作为参数传递,并且当堆栈“展开”时不需要任何操作,因为最后 n * m 计算返回完整答案。

【讨论】: