我很想知道,如何重写"常规"递归函数作为尾递归函数?有某种策略吗?
例如:我有这个简单的函数,我想把它变成一个尾递归的函数:
int rec(int n) {
if (n % 2 || n > 10) {
return n;
}
return rec(n+1) + rec(n+2);
}
感谢任何帮助。
答案 0 :(得分:6)
答案取决于你对“尾递归”函数和“重写”函数的定义,但我将假设这些方面的大部分非正式理解:
return
语句中整个表达式的调用)。那个偏僻。 。 。你应该知道,并不总是能够以尾递归的方式重写函数。您自己的示例在非基本情况下具有函数make 两个递归调用,我们显然无法将它们的两个更改为{{}中的整个表达式1}}语句,所以你的例子可以被重写为尾递归的唯一原因是我们可以消除其中一个调用。
所以,让我们从你的功能开始:
return
现在,如果int rec(int n) {
if (n % 2 || n > 10) {
return n; // line 3
}
return rec(n+1) + rec(n+2); // line 5
}
是奇数,那么我们会在第3行返回n
;所以,如果我们到达第5行,那么我们就知道n
必须是均匀的。这意味着如果我们到达第5行,则n
为奇数,n+1
为rec(n+1)
。所以我们可以消除一个递归调用:
n+1
接下来,它有助于扩展一个真实的例子,看看它是什么样的:
int rec(int n) {
if (n % 2 || n > 10) {
return n;
}
return (n+1) + rec(n+2);
}
一个重要的见解是,由于加法是关联的,我们可以用相反的方式对术语进行分组:
rec(6) = 7 + rec(8)
= 7 + (9 + rec(10))
= 7 + (9 + (11 + rec(12)))
= 7 + (9 + (11 + (12)))
这很有用,因为这意味着我们可以在继续时计算结果的部分和,并将其作为单独的参数传递:
rec(6) = ((7 + 9) + 11) + 12
。 。 。现在我们有一个尾递归函数,但它要求客户传递额外的参数!要解决这个问题,我们只需将其重命名为int rec(int n, int sum_so_far) {
if (n % 2 || n > 10) {
return sum_so_far + n;
}
return rec(n+2, sum_so_far + (n+1));
}
并将其包装在一个函数中以供客户调用:
rec_helper
如您所见,没有一般策略;相反,我们需要分析函数,并利用有关整数的事实,消除一个递归调用,然后我们需要再次做同样的事情将另一个递归调用转换为尾递归调用。
然而,我们所做的一个方面是一个非常常见的模式,即将递归移动到一个带有附加参数的辅助函数,其中该参数包含到目前为止已计算的部分结果。我会说在构造尾递归函数时,我至少有90%的时间使用此模式。
答案 1 :(得分:1)
您需要从基本案例开始,并查看最接近基本案例的默认案例的模式。在您的情况下,所有基本情况都会计算到n为11,因此与基本情况最接近的默认情况为9
。下一个是7
,它包含9
,因此我们有这种模式:
n 0 1 2 3 4 5 6 7 8 9 10 11 12 13 ...
r 0 | 2 | 4 | 6 | 8 | 10 11 12 13
| | | | 10+11
| | | 8+10+11
| | 6+8+10+11
| 4+6+8+10+11
2+4+6+8+10+11
基于此显而易见,11以下的所有奇数都成为后继者和所有偶数的总和,最多10加11。
对于迭代尾递归版本,我建议使用类似的东西:
int rec_helper(int n, int acc) {
if (n == 12) {
return 11+acc;
}
return rec_helper(n+2, acc+n);
}
int rec(int n) {
if (n % 2 || n > 10) {
return n;
}
return rec_helper(n+1, 0);
}
在这种情况下,虽然这是矫枉过正。请注意,除11之外的系列是Arithmetic progression,并且总和定义良好。结果根本不需要递归:
int rec(int n) {
if (n % 2 || n > 10) {
return n;
}
return (121-n*n)/4;
}