将递归函数转换为尾递归函数

时间:2015-10-08 16:18:21

标签: c++ loops recursion tail-recursion

连续this问题:

我在c ++中有一个一遍又一遍地调用自己的函数。就是这样:

#include <iostream>
#include <math.h>
#include <stdio.h>
#include <cmath>
using namespace std;
double g( double a, double x){
    if (x>=a) return (g(a,x-1)+g(a,x-a));
    else if (x<a) return 1;
    return 0; //Never Reached
}
int main(){
    cout << (unsigned long)g(sqrt(90),90) <<endl; // outputs 7564511
    cout << (unsigned long)g(sqrt(10000019),10000019)<<endl; // Signal: SIGSEGV (Segmentation fault)
}

我想知道如何将此函数转换为某种迭代或尾循环(或任何阻止段错误的东西),但更重要的是我需要知道如何自己实际执行此操作。

注意: 如果这是一个微不足道的问题,我会提前道歉。

注意2: 有类似的问题(例如thisthis),但我发现的问题都没有说明我的事实函数每次迭代调用两次。

3 个答案:

答案 0 :(得分:3)

Memorization可以是限制计算所需的递归调用次数的有效方法。尝试评估一些简单输入的函数,例如g(2, 8),您将看到最终一遍又一遍地评估相同值的函数。通过在第一次计算时为每组输入缓存结果,可以使递归短路并显着减小问题的大小。

要使函数迭代而不是递归,您可以使用的一种策略是尝试转换定义并从下往上迭代。考虑Fibonacci函数:

fib(n) = fib(n-1) + fib(n-2)

要迭代计算fib(n),我们从基本案例fib(1) + fib(0)开始,并迭代到fib(n)。这使您可以随时累积值,而不必在计算中间值时记住您的位置(一遍又一遍)。所以fib()的迭代定义如下:

fib(n) {
    a = 1;
    b = 0;
    fib = 0;
    i = 1;
    while (i < n) {
        fib = a + b;
        b = a;
        a = fib;
        i++;
    }
    return fib;
}

您应该可以使用g()功能执行类似的操作。我没有时间玩它,但我敢打赌,如果您尝试手动评估一些a, x对,您会注意到一种模式,让您重写我以上面对fib()所做的方式以迭代形式运行。

答案 1 :(得分:1)

正如许多人已经说过的那样,这不能直接转换为尾递归或迭代函数,因为浮点函数参数使迭代构建结果变得困难。但是,通过一些思考,可以非常有效地计算函数。

首先,因为所有递归都是求和的并以1结尾,所以该函数基本上计算了递归结束的路径数。例如,对于g(5,2),一条路径将是g(2,5) - > g(2,3) - > g(2,1)(这里返回1)和另一路径g(5,2) - &gt; g(4,2) - &gt; g(3,2) - &gt; g(2,2) - &gt;克(0,2)。因此,为了计算g,我们需要计算可能路径的数量。

让我们从我们总是减去x的路径开始。显然,我们只有一条这样的道路。接下来,考虑我们一旦选择减去1的路径和减去a的其他时间的情况,我们有楼层((xa)/ a)位置来选择1.因此,有楼层((xa)/ a )在这种情况下可能的路径。在下一次迭代中,我们想要选择步骤2两次。存在n *(n-1)/ 2组合,其中n = floor((x-1-a)/ a)并且n *(n-1)/ 2是二项式系数\ binom {n,2}。下一步有三个,有\ binom {n,3}组合,其中n现在= floor((x-2-a)/ a)等。

如果预先计算二项式系数,则算法为O(x),因此它也可能计算g(sqrt(10000019),10000019)。但是,最大的本机c ++整数类型(unsigned long long)溢出已经在g(sqrt(500),500)附近。您可以使用long double来获得稍大输入的近似值。或者你可以使用boost Multiprecision Library来获得更多的数字,但我猜你在得到g(sqrt(10000019),10000019)的答案之前会耗尽内存。

带溢出检查的源代码来计算g()

#include <iostream>
#include <vector>
#include <limits>
#include <algorithm>
#include <cstdlib>

unsigned long long binomial(unsigned int n, unsigned int m) {
  if (n - m < m) m = n - m;
  std::vector<unsigned long long>bc(m+1, 0);
  bc[0] = 1;
  for (unsigned int i = 0;i <= n;++i) {
    for (unsigned int j = std::min(i, m);j > 0;j--) {
      if (std::numeric_limits<unsigned long long>::max()-bc[j-1] < bc[j]) {
        std::cout << "Error: too large binomial coefficient(" << n << "," << m << ")" << std::endl;
        exit(1);
      }
      bc[j] += bc[j - 1];
    }   
  }
  return bc[m];
}

unsigned long long g(double a, double x) {
  unsigned long long r = 1;
  int n = 0;
  for (int i = static_cast<int>(x);i >= a;--i) {
    ++n;
    int m = static_cast<int>((i - a) / a);
    unsigned long long b = binomial(m + n, n);
    if (std::numeric_limits<unsigned long long>::max() - b < r) {
      std::cout << "Error: too large sum " << b << "+" << r << std::endl;
      exit(1);
    }
    r += b;
  }
  return r;
}

int main(){
  std::cout << g(sqrt(90), 90) << std::endl;
  std::cout << g(sqrt(10000019), 10000019) << std::endl;
}

答案 2 :(得分:0)

我想仅供参考,这里的实现没有递归。我不确定它是否会以给定的输入完成:

#include <stack>
double gnr(double a, double x) {
   std::stack<double> stack;
   double result = 0;
   stack.push(x);
   while (!stack.empty()) {
      x = stack.top();
      stack.pop();
      //cout << stack.size() << " " << x << endl;
      if (x < a) {
         result++;
      } else {
         stack.push(x - 1);
         stack.push(x - a);
      }
   }
   return result;
}