【问题标题】:Is this function really tail-recursive?这个函数真的是尾递归的吗?
【发布时间】:2013-04-20 01:43:04
【问题描述】:

我在 Programming Interviews Exposed(第 3 版)中读到了递归,其中介绍了以下递归 factorial 函数:

int factorial(int n){
    if (n > 1) { /* Recursive case */
        return factorial(n-1) * n;
    } else {     /* Base case */
        return 1;
    }
}

在同一页的底部(第 108 页),他们谈到了尾递归函数:

注意,当递归调用返回的值本身立即返回时,如前面factorial的定义,函数是tail-recursive

但这里真的是这样吗?函数中的最后一个调用是* 调用,所以这个栈帧不会被保留(如果我们不考虑编译器优化)?这真的是尾递归吗?

【问题讨论】:

  • 这个函数确实不是尾递归的。
  • 阶乘的朴素递归实现是非尾递归递归函数的经典示例,通常用于说明如何通过使用累加器获得尾递归......我建议你会得到一本不同的书。
  • 你写“所以不会保留这个堆栈帧(如果我们不考虑编译器优化)” - 但是编译器优化与 this 函数无关,因为堆栈帧不能被消除...正是因为函数不是尾递归的。 Eric Jablow 给出了一个尾递归的函数,因此堆栈帧可以被优化编译器消除。优化对于 C C++ 并不是那么重要,但对于像 F# 和 Haskell 这样的函数式语言来说是必不可少的,其中循环通常以递归方式实现。
  • @JimBalter:所以我认为没有编译器会优化它以使用累加器,从而使其成为尾递归? (实际上,确实听起来有点过头了,现在我想起来了……)
  • 我对此表示怀疑,因为这样的重写可以任意复杂,具体取决于功能。

标签: c++ c tail-recursion


【解决方案1】:

您可以将其重写为尾递归:

int factorial(int n){
    return factorial2(n, 1);
}
int factorial2(int n, int accum) {
    if (n < 1) {
       return accum;
    } else {
        return factorial2(n - 1, accum * n);
    }
}

【讨论】:

    【解决方案2】:

    不,它不是尾递归的。 factorial(n-1) 返回的结果仍然需要乘以 n,这要求 factorial(n) 重新获得控制权(因此要求对 factorial(n-1) 的调用是调用而不是跳转)。

    话虽如此,即使它是尾递归的,编译器仍然可能不会对其进行 TCO。取决于编译器和您要求它执行的优化。

    【讨论】:

    • 至少书上说“一些编译器可以执行尾调用消除......”是正确的。
    • 对。所以这本书说这个函数是尾递归是完全错误的。谢谢!
    • 这本书说所有递归函数都可以迭代编写,这也是一种误导。这在技术上是正确的,因为您始终可以使用堆栈来模拟递归,但这并不是同一函数的真正迭代形式。许多递归函数没有直接的迭代对应项。
    【解决方案3】:

    引用自此链接:tail recursion using factorial as example

     factorial(n) {
        if (n == 0) return 1;
        return n * factorial(n - 1);
     }//equivalent to your code
    
     This definition is NOT tail-recursive since the recursive call to 
     factorial is not the last thing in the function 
     (its result has to be multiplied by n)
    

    【讨论】:

      【解决方案4】:

      Tail Recursive 是递归的一种特殊情况,其中函数的最后一个操作是递归调用。在尾递归函数中,从递归调用返回时没有待执行的操作。 您提到的函数不是尾递归,因为有一个待处理的操作,即在递归调用返回时要执行乘法。 如果你这样做了:

          int factorial(int n,int result)
          {
              if (n > 1)
              { /* Recursive case */
                   return factorial(n-1,n*result);
              }
              else
              {     /* Base case */
                  return result;
              }
          }
      

      将是一个尾递归函数。因为它在从递归调用返回时没有挂起的操作。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-09-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-12-10
        相关资源
        最近更新 更多