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

使用多个递归调用将正常递归转换为尾部递归

梁丘招
2023-03-14

我试图了解如何将各种递归函数转换为尾递归。我已经查看了许多将斐波那契和阶乘转换为尾递归的示例,并理解了这些示例,但很难跳到具有某种不同结构的问题。一个例子是:

def countSteps(n: Int): Int = {
  if(n<0) return 0
  if(n==0) return 1
  countSteps(n-1) + countSteps(n-2) + countSteps(n-3)
}

如何将其转换为尾部递归实现?

我已经看过类似的问题,例如:将正常递归转换为尾部递归,但这些似乎并没有转化为这个问题。

共有2个答案

赫连鸿振
2023-03-14

将函数或任何需要多次调用自身的函数转换为尾部递归函数的诀窍是将多次调用分解为可以递归使用和应用的参数列表:

def countSteps(n: Int): Int = {
  def countSteps2(steps: List[Int], acc: Int): Int =
    steps match {
      case Nil => acc
      case head :: tail =>
        val (n, s) = if (head < 0) (0, Nil)
        else if (head == 0) (1, Nil)
        else (0, List(head - 1, head - 2, head - 3))
        countSteps2(s ::: tail, n + acc)
    }
  countSteps2(List(n),0)
}

内部函数count tSteps2不再接受单个参数,而是一个参数列表和一个累加器。通过这种方式,我们可以计算参数头部的值或生成一个新的参数列表,而不是添加到现有序列中以使count tSteps2递归。

每次有输入时,我们都会取头,然后计算0、1或其他参数列表。现在,我们可以再次递归countSteps2,将新的参数列表添加到现有的尾部,并将我们计算的任何值添加到累加器中,即acc。由于对countSteps2的唯一调用是退出条件,因此递归是尾部递归

当所有输入都已消耗完时,我们最终可以退出,此时acc将所有递归步骤的结果相加。

鲜于华容
2023-03-14

有些东西不是尾部递归的,任何转换它们的尝试最终都会手动构建堆栈。

然而,在这种情况下,我们可以累积(未经测试,没有scala):

def countSteps(n: Int): Int = {
  if (n < 0) return 0
  countStepsAux(n, 0, 1, 0, 0)
}

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

  • 我想知道是否有一些通用方法可以用foo(…)转换“正常”递归foo(…) 作为尾部递归的最后一个调用。 例如(scala): 函数语言将递归函数转换为等价尾部调用的一般解决方案: 一种简单的方法是将非尾部递归函数包装在蹦床单子中。 所以pascal函数不再是递归函数。然而,蹦床单子是需要完成的计算的嵌套结构。最后,是一个尾递归函数,它遍历树状结构,解释它,最后在基本情况下返回值。 Rúnar Bj

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

  • 我想我理解了教科书中对尾部递归函数的定义:在函数调用后不执行任何计算的函数。我还发现,作为一个结果,尾部递归函数的内存效率会更高,因为它每次调用只需要一条记录,而不是每次都需要保留一条记录(就像在普通递归中那样)。 我不太清楚的是,这个定义如何应用于嵌套调用。我将提供一个例子: 我最初给出的答案是,根据定义,它不是尾部递归的(因为外部调用是在计算内部调用之后执行的,所以其他计算是在第一次调用之后完

  • 我对函数式编程很陌生,尤其是下面使用的Scheme。我正在尝试使以下函数是递归的,尾递归的。基本上,该函数的作用是对两个字符串的对齐方式进行评分。当给定两个字符串作为输入时,它会比较每个“列”字符,并根据在称为 scorer 的函数中实现的评分方案(由下面的代码中的函数调用)来累积该对齐的分数。 我有一个想法,用一个帮助函数来累积分数,但我不太确定如何去做,因此我该如何让下面的函数尾递归呢?

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