将递归函数转换为尾递归函数
Convert a recursive function to a tail recursive function
关于这个问题:
我在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)
}
我想知道如何将这个函数转换成某种迭代或尾循环(或任何停止分段错误的东西),但更重要的是,我需要知道如何实际自己做。
注意:如果这是一个微不足道的问题,我提前道歉。
NOTE2:有类似的问题(像这个或这个),但我发现没有一个解决我的函数每次迭代调用自己两次的事实。
记忆可以是限制计算所需递归调用数量的有效方法。试着对一些简单的输入求值,比如g(2, 8)
,你会发现你最终会对相同的值一次又一次地求值。通过在您第一次计算时缓存每组输入的结果,您可以缩短递归并显着减小问题的大小。
要使函数迭代而不是递归,您可以使用的一种策略是尝试反转定义并从下向上迭代。考虑斐波那契函数:
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结束,所以函数基本上是计算到递归结束的路径数。例如,对于g(5,2),一条路径是g(2,5)-> g(2,3) -> g(2,1)(这里返回1),另一条路径是g(5,2)-> g(4,2) -> g(3,2) -> g(2,2) -> g(0,2)。为了计算g,我们只需要计算可能路径的个数。
让我们从x减去a的路径开始,显然我们只有一条这样的路径。接下来,考虑这样一种情况,当我们选择路径时减去1,其他时候减去a,我们有floor((x-a)/a)的位置来选择1。因此,在这种情况下,有1 ((x-a)/a)条可能的路径。在下一次迭代中,我们希望选择步骤1两次。有n*(n-1)/2组合,其中n=floor((x-1-a)/a), n*(n-1)/2是二项式系数binom{n,2}。下一步有三个1有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;
}
我想只是作为参考,这里是实现无递归。我不确定它是否会完成给定的输入:
#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;
}
- 递归函数计算序列中的平方和(并输出过程)
- 如何在Elixir中调用递归函数并行
- 递归函数有效,但无法记忆
- 使用递归函数 (c++) 将长字符串转换为整数时输出错误
- 如何将递归函数(具有两个基本情况)转换为迭代函数
- 将递归函数转换为迭代函数
- 转换递归函数
- Python 3.3 如何将这个递归函数转换为纯收益循环版本?
- 将这个糟糕但功能强大的代码转换为递归函数
- 在turbo c++中,可以将一个普通递归函数转换为尾递归函数来对其进行优化
- 将迭代函数转换为递归函数
- 将迭代函数转换为递归函数
- 如何从递归函数返回指针,该函数将抛光表示法转换为反向抛光表示法
- 递归函数的转换
- 转换递归函数并使其迭代
- 将递归函数转换为尾递归函数
- 将迭代函数转换为递归函数
- C++ |需要帮助从 for 循环函数转换 - >递归函数
- 这个递归函数是如何自动转换成迭代函数的
- 在递归函数中,c++基转换过程中意外输出到屏幕