将公共递归转换为尾递归,因为大型输入的堆栈溢出

converting a common recursion to tail recurtion because of stack overflow for large inputs

本文关键字:大型 因为 输入 栈溢出 堆栈 尾递归 递归 转换      更新时间:2023-10-16

我发现这个算法用于一个程序,该程序返回具有特定周长的可能三角形的数量。我知道这个问题还有其他一些方法,但使用它们,我得到大数字的时间限制错误,可能是因为它们没有优化。这个似乎更优化,但我得到了堆栈溢出以进行大输入的递归。任何人都可以通过将其转换为尾递归或对此问题做其他事情来帮助我优化它吗?

这是我找到的算法:

#include<iostream>
using namespace std;
int foo(int);
int main(){
int n;
cin>>n;
cout<<foo(n);
return 0;
}
int foo(int n){
int p=n/2-2;
int t;
if(p%6<5&&p%6>0)
t=(p+6-p%6)/6;
else if(p%6==5)
t=(p+6-p%6)/6+1;
else
t=p/6;
if(n==3||n==5||n==6)
return 1;
else if(n==4)
return 0;
else{
if(n%2==0)
return foo(n-3);
else
return foo(n+1)+t;
}
}

问题就在这里:

return foo(n+1) + t;
//              ^^^

加法使得递归调用foo不是函数中发生的最后一件事。因此,您需要将该添加项移动到函数

return foo(n + 1, t);

为了使另一个递归调用兼容,您只需提供 add 的中性元素:

return foo(n-3, 0);

新函数的签名:

int foo(int n, int offset = 0); // default parameter allows for calling just as before.

剩下的最后一个问题:在哪里添加???

嗯,有两个地方很明显:

if(n==3||n==5||n==6)
return 1 + offset;
else if(n==4)
return /*0 +*/ offset;

offset是所有先前递归调用的累积偏移量!因此,您必须将其添加到添加到下一个函数的递归调用的任何内容中,因此:

// ...
else if(n % 2 == 0)
return foo(n-3, /*0 +*/ offset);
else
return foo(n+1, t + offset);

旁注:您的算法似乎不适合负数。使用这些调用(未修改的(foo最终会重复出现,直到堆栈溢出(如果堆栈足够大,则由于有符号整数溢出,可能会导致未定义的行为(。如果您不打算在负数上使用它,则绝对应该使用unsigned int作为数据类型!否则,它需要适当的修复。

值 0、1 和 2 也需要特殊处理,它们会导致负n(对于未修改的foo,见上文(。但你几乎可以轻而易举地做到这一点:

if(n == 4)
{ ... }
else if (n < 6)
{ ... }

这将返回 1 对于 n 为 0、1 或 2(即使使用尾部调用优化版本,因为偏移量仍然是 0(,甚至省去一些比较。如果需要为特殊值返回 0:也很简单:

else if (n < 6)
return (n >= 3) + offset;

您甚至可以将所有内容合并为一个条件:

if(n < 6)
return (n == 3 || n > 4) + offset; // returning 0 for 0, 1, 2
return (n <= 3 || n > 4) + offset; // returning 1 for 0, 1, 2

最大的优势:在所有这些递归期间,您只需要一次检查,而所有其他检查仅在满足停止条件时才执行(即仅一次(。

您可以将pt的计算移动到停止条件后面,无论如何您都不会在那里使用它们......