当前位置: 首页 > 知识库问答 >
问题:

静态编程语言-将函数标记为尾递归但未找到尾调用

吕亮
2023-03-14

我有一个用递归计算斐波那契级数的函数。

fun fibonacciRecursive(n: Long): Long {
    if (n <= 1) {
        return n
    }

    return fibonacciRecursive(n - 1) + fibonacciRecursive(n - 2)
}

这是可行的,但如果数字很大,那么执行起来需要很长时间。我听说kotlin有一个关键字tailrec,它改进了递归并将函数重写为循环。但是当我将这个关键字添加到这个函数中时,编译器告诉我们,一个函数被标记为尾部递归,但没有找到尾部调用。为什么我不能添加tailrec?

共有2个答案

易宏阔
2023-03-14

如果要使用尾部递归,通常需要相应地编写算法。对于斐波那契函数,将其转换为尾部递归函数的最简单方法可能是向上计算,而不是向下计算:

(不幸的是,我不知道Kotlin,所以下面的代码是公共Lisp)。

(defun fibo-slow (n)
  "The classic, non- tail recursive way..."
  (if (<= n 1)
      n
      (+ (fibo-slow (- n 1))
         (fibo-slow (- n 2)))))

(defun up (n i-1 i-2 i)
  "Tail recursive 'kernel', passing the history as arguments."
  (if (= i n)
      (+ i-1 i-2)
      (up n (+ i-1 i-2) i-1 (+ i 1))))

(defun fibo-up (n)
  "The adapter for function up, so the interface remains the same as 'fibo-slow'."
  (case n
    (0 0)
    (1 1)
    (otherwise 
     (up n 0 1 1))))

如您所见,在fibo-slow中,尾部位置是加法(),而不是对递归函数的调用。

子车睿
2023-03-14

尾部递归是一种递归类型,在每个级别上,递归调用后不需要进行任何计算-它只调用更深层的杠杆,最深层的结果是整个调用堆栈的结果。

本质上,您可以忘记每个中间级别,当您到达“底部”时,只需获取结果并立即将其提供给原始调用方。

在你的例子中,你必须调用下一个级别,等待它,然后是另一个,等待它,然后求和——这意味着你没有尾递归实现,因为你必须在递归调用后的中间级别做一些事情。

斐波那契序列(与所有递归解一样)可以用一个规则循环迭代求解,该循环不需要大的调用堆栈,最终也会减慢速度。尝试这种实现是一种很好的做法,尽管根据定义,这个序列“很难”计算,并且很快需要多次迭代。这还可以提示您如何将其重新编写为尾部递归:

  1. 每次迭代n给定x\u n和x\u n-1,设置x\u n-1=x\u n,x\u n=x\u n x\u n-1(或类似的东西)
  2. 这样做直到达到所需的迭代量,在那里立即得到最终的总和

在这里,我们不需要“返回”来完成计算。因此,在尾部递归的实现中,而不是只传递迭代索引,您需要传递x\n和x\n x\n-1,因此在最后一级,您已经有了总和-无需“返回”。尾部递归本质上是可以轻松转换为循环的递归。

 类似资料:
  • 我有一个家庭作业问题,它给出了一个递归函数,我必须使用尾部递归来实现它。函数为f(0)=1 f(n)=1 2*f(n-1) 我并不擅长尾部递归,我试着查找示例,但我发现的都是没有斐波那契序列的示例,这没有多大帮助。 我真正拥有的是 我知道尾递归基本上每次调用都计算函数,我只是不知道如何实现它。 编辑:我打了一个错字f(n)应该是1 2*f(n-1)

  • 本文向大家介绍C#函数式编程中的递归调用之尾递归详解,包括了C#函数式编程中的递归调用之尾递归详解的使用技巧和注意事项,需要的朋友参考一下 关于递归相信大家已经熟悉的不能再熟悉了,所以笔者在这里就不多费口舌,不懂的读者们可以在博客园中找到很多与之相关的博客。下面我们直接切入正题,开始介绍尾递归。 尾递归 普通递归和尾递归如果仅仅只是从代码的角度出发来看,我们可能发现不了他的特点,所以笔者利用两张堆

  • 我只是想知道这样的函数是否可以递归地完成。我觉得很难,因为它自己叫了两次。 这是我在javascript中的非尾部递归实现。(是的,我知道大多数javascript引擎不支持TCO,但这只是理论上的。)目标是找到给定数组(arr)中具有特定长度(大小)的所有子列表。例如:getSublistsWithFixedSize([1,2,3],2)返回[[1,2]、[1,3]、[2,3]]

  • 我有一个关于如何将“递归”转换为“尾部递归”的问题。 这不是家庭作业,只是在我试图润色算法书籍中的递归定理时出现的一个问题。 我熟悉使用递归的两个典型示例(阶乘和斐波那契序列),也知道如何以递归方式和尾部递归方式实现它们。 我的代码如下(我使用Perl只是为了使其简单,但可以轻松地转换为C/Java/C)。 运行代码时,输出如下: 递归函数在返回之前使用不同的参数调用自己两次。我尝试了几种方法将其

  • 尾递归优化 recur 尾递归优化是函数式编程不能缺少的一个性能优化方案. 没有尾递归, 常有的递归调用也会形成很深的调用栈消耗内存. cljs 和 Clojure 类似, 都需要通过声明 recur 进行优化. 最终代码会被编译为 white 结构的 js 代码,从而提高性能. (defn factorial [acc n] (if (<= n 1) acc (recur (* ac

  • 我在Kotlin中编写了这个递归函数: 它将运行不确定的次数(因为我在进化算法中使用随机性来收敛解)。我经常得到堆栈溢出。静态编程语言/JVM语言的最大堆栈深度是多少?我应该只编写非递归函数吗?