【问题标题】:Is it possible to have stack overflow by using recursive function?使用递归函数是否可能发生堆栈溢出?
【发布时间】:2011-12-09 15:11:30
【问题描述】:

这个功能有问题?

 unsigned long factorial(unsigned long m)
 {
     return (m == 0) ? 1 : m * factorial(m - 1);
 }

如果我添加另一个功能:

  int myCombina(int q, int p)
  {
      return factorial(q) / ( factorial(q) * factorial(q-p) );
  }

此 myCombina() 效率不高,因为应在其中取消最大公约数以查找组合。

max(factorial(q), factorial(q-p)) 可以取消。 我们只需要计算 q x (q-1) ... x (q -k +1)。

还有其他问题吗?

欢迎任何 cmets。

谢谢

【问题讨论】:

  • 在任何不支持尾调用优化的语言中,阶乘的递归实现也将是极其低效的。迭代解决方案将仅使用 O(1) 临时存储,并且通常会运行得更快。
  • 明显的语言是 C/C++,所以支持尾调用优化。 stackoverflow.com/questions/34125/…
  • @DanielPryden:实际上,如果你使用 bignum 类型,它的存储量是 O(log m)。
  • @gkuan:“支持”与“保证”不同。没有任何自尊的 C 或 C++ 编程会依赖于生产代码中的无限递归(除非深度为 O(log n))。
  • @DanielPryden tail-call optimization 既是一种语言特性,也是一种代码特性,在这种特殊情况下,它不能被应用(如果你返回 m*factorial(m-1)调用必须在乘法完成之前返回,这意味着递归不能转换为循环。现在,如果它被转换为允许优化(传递一个带有临时结果的额外参数),那么编译器可以执行优化.

标签: c++ c recursion stack stack-overflow


【解决方案1】:

如果 m 很大,可能会发生堆栈溢出。

堆栈溢出不是您的代码的主要问题...如果 m 非常大,您将在堆栈溢出之前得到 integer overflow

如果您希望它适用于大于约 12 的 m(取决于您平台上 unsigned long 的大小),您需要使用某种 Bignum 类型。

【讨论】:

    【解决方案2】:

    它不是以尾递归形式编写的,因此即使编译器支持适当的尾调用优化,您也不会得到好处。

    【讨论】:

    • 嗯?为什么不?你觉得尾部恢复形式会是什么样子?
    • 我看不到该链接如何回答我的问题。 OP 的代码看起来完全可以优化尾调用。
    • 考虑到尾调用优化时,递归版本可以与迭代版本一样快。这个例子不应该是为什么在所有情况下都应该避免递归的笼统例子。
    • 这个版本不是尾递归的,因为它递归地调用阶乘,然后仍然需要将结果与 m 相乘。因此,它不能按原样变成循环。尾调用位置意味着任何递归调用所做的最后一件事是递归调用,而不是其他一些操作。
    • @yi_H:该链接准确地回答了您提出的问题。接受的答案提出了两个递归阶乘函数,并非常清楚地解释了为什么一个是尾递归而另一个不是。
    【解决方案3】:

    该函数实际上会导致堆栈溢出(每个级别的递归都会消耗一些堆栈,直到它全部消耗完)。

    正如其他人所提到的,您可以将递归函数转换为循环,在这种情况下这很简单,或者您可以修改递归以允许尾调用优化(让编译器将递归转换为循环) .

    只是为了它,要转换为尾递归调用,函数的最后一条语句必须是从递归调用获得的结果的return。你的代码不能被优化掉,因为你的return语句包含m*factorial(n-1),也就是说,你没有返回递归的值,而是在返回之前实际操作它。

    使其尾递归的转换需要将乘法下推到递归调用中,这通常作为保留临时结果的额外参数执行:

    unsigned long factorial_tail_recursive( 
                            unsigned long n,           // number to calculate factorial
                            unsigned long temp_result  // accumulated result
                          ) 
    {
       return n == 0? tmp_result 
                    : factorial_tail_recursive( n-1, n*temp_result );
    }
    
    // syntactic sugar to offer the same interface
    unsigned long factorial( unsigned long n ) {
       return factorial_tail_recursive( n, 1 );    // accumulated result initialized to 1
    }
    

    但话又说回来,对于那个特定问题,一个有效的迭代解决方案可能更容易得到正确的解决方案。只需将累积的结果保存在一个临时变量中并循环,直到参数减少到 1。

    【讨论】:

      猜你喜欢
      • 2018-05-28
      • 2019-08-25
      • 2017-10-20
      • 2014-03-19
      • 2011-02-26
      • 1970-01-01
      相关资源
      最近更新 更多