Algorithm 将递归转换为';尾部递归';
我有一个关于如何将“递归”转换为“尾部递归”的问题。 这不是一个家庭作业,只是当我试图润色算法书中的递归定理时弹出的一个问题。 我熟悉使用递归的两个典型示例(阶乘和斐波那契序列),也知道如何以递归方式和尾部递归方式实现它们。 我的代码如下所示(我使用Perl只是为了简化它,但可以很容易地转换为C/Java/C++) 递归函数在返回之前使用不同的参数调用自身两次; 我尝试了几种方法将其转换为尾部递归方式,但结果都是错误的 有人能看一下代码并告诉我如何使其尾部递归吗?特别是我相信有一个转换这个树递归的例程(在返回之前多次调用递归函数),有什么可以解释一下吗?所以我以后可以用同样的逻辑来处理不同的问题。Algorithm 将递归转换为';尾部递归';,algorithm,recursion,Algorithm,Recursion,我有一个关于如何将“递归”转换为“尾部递归”的问题。 这不是一个家庭作业,只是当我试图润色算法书中的递归定理时弹出的一个问题。 我熟悉使用递归的两个典型示例(阶乘和斐波那契序列),也知道如何以递归方式和尾部递归方式实现它们。 我的代码如下所示(我使用Perl只是为了简化它,但可以很容易地转换为C/Java/C++) 递归函数在返回之前使用不同的参数调用自身两次; 我尝试了几种方法将其转换为尾部递归方式,但结果都是错误的 有人能看一下代码并告诉我如何使其尾部递归吗?特别是我相信有一个转换这个树递归
提前感谢。使用谷歌,我找到了这个描述。基本上,您需要将函数拆分为至少两个其他函数:一个用于执行工作,保持当前值的“累积”,另一个用于驱动您的济贫院函数。C中的阶乘示例如下:
/* not tail recursive */
unsigned int
factorial1(unsigned int n)
{
if(n == 0)
return 1;
return n * factorial1(n-1);
}
/* tail recursive version */
unsigned int
factorial_driver(unsigned int n, unsigned int acc)
{
if(n == 0)
return acc;
/* notice that the multiplication happens in the function call */
return factorial_driver(n - 1, n * acc);
}
/* driver function for tail recursive factorial */
unsigned int
factorial2(unsigned int n)
{
return factorial_driver(n, 1);
}
你可以这样做:
#include <stdio.h>
void fr(int n, int a[])
{
int tmp;
if (n == 0)
return;
tmp = a[0] * a[2] + 1;
a[2] = a[1];
a[1] = a[0];
a[0] = tmp;
fr(n - 1, a);
}
int f(int n)
{
int a[3] = { 1, 1, 1 };
if (n <= 2)
return 1;
fr(n - 2, a);
return a[0];
}
int main(void)
{
int k;
for (k = 0; k < 10; k++)
printf("f(%d) = %d\n", k, f(k));
return 0;
}
void fr(int n, int a[])
{
int tmp;
label:
if (n == 0)
return;
tmp = a[0] * a[2] + 1;
a[2] = a[1];
a[1] = a[0];
a[0] = tmp;
n--;
goto label;
}
编译器可以将fr()
转换为如下内容:
#include <stdio.h>
void fr(int n, int a[])
{
int tmp;
if (n == 0)
return;
tmp = a[0] * a[2] + 1;
a[2] = a[1];
a[1] = a[0];
a[0] = tmp;
fr(n - 1, a);
}
int f(int n)
{
int a[3] = { 1, 1, 1 };
if (n <= 2)
return 1;
fr(n - 2, a);
return a[0];
}
int main(void)
{
int k;
for (k = 0; k < 10; k++)
printf("f(%d) = %d\n", k, f(k));
return 0;
}
void fr(int n, int a[])
{
int tmp;
label:
if (n == 0)
return;
tmp = a[0] * a[2] + 1;
a[2] = a[1];
a[1] = a[0];
a[0] = tmp;
n--;
goto label;
}
这就是尾部调用优化。尽管您经常会看到以下将阶乘转换为尾部调用的示例:
int阶乘(int n,int acc=1){
if(n@Alexey Frunze的答案是可以的,但并不完全正确。通过将任何程序转换为
我现在没有时间,但如果我有几分钟的时间,我会尝试在CPS中重新实现您的程序。问题是,最后一个操作不是递归调用,而是乘法中加1。您在C中的函数:
unsigned faa (int n) // Ordinary recursion
{
return n<3 ? 1 :
faa(n-3)*faa(n-1) + 1; // Call, call, multiply, add
}
真正的尾部递归要求您自己处理堆栈、在汇编中编写堆栈或拥有编译器支持。我不相信Perl目前支持它。我认为@Dai you与尾部递归删除混淆了,尾部递归删除是一种编译器优化。我想他只是要求转换为尾部递归函数。这里的关键点是递归调用是在驱动程序中发生的最后一件事,因此可以用跳转到函数的入口点来替换调用。C编译器是否优化了该函数调用?我不这么认为。它在其他语言中是自动完成的,但在C中,您必须使用某种loop@comocomocomocomo:大多数C编译器都会进行尾部调用优化,至少对简单的尾部调用是这样。根据我的经验,gcc做得很好。哦!很高兴知道。谢谢,@rici谢谢,Alexey,到目前为止,这是我得到的最好答案,至少你提供了可行的、可验证的代码。rici的答案更接近你想要的,而且也很有效。我给了他+1恐怕您第二段中的声明是错误的——请参阅Tosi的回答,以查看如何使用累加器参数将OP的非尾部递归转换为尾部递归的示例。@j_random_hacker所有声明均已删除。(我现在意识到Tosi的答案只解决了更简单的阶乘问题,但正如你所说,rici的非常好的答案也涉及Fibonacci。)非常好的答案!我认为Fibonacci“双重递归”可以转化为纯尾部递归,因为这个特殊的问题可以在O(1)-空间中使用迭代解来解决,但是(如果我错了,请纠正我)并非所有看起来类似于初始斐波那契递归的问题都可以以相同的方式转换为纯尾递归——例如,如果没有(隐式或显式)的堆栈。这是正确的吗?如果是,有没有一种很好的方法来描述哪些问题像斐波那契,因为它们可以简化为纯尾部递归?谢谢,@rici,你的答案非常简洁,我很喜欢。请你解释一下你是如何想出解决方案的?对我来说,递归调用“返回鼠标”的那一行(n-1,a*c+1,a,b);“就像一个魔术,我可以看到它,但不太明白你是如何从原始的递归公式中得出它的。提前谢谢!
mouse(0) = 1
mouse(1) = 1
mouse(2) = 1
mouse(3) = 2
mouse(4) = 3
mouse(5) = 4
mouse(6) = 9
mouse(7) = 28
mouse(8) = 113
mouse(9) = 1018
unsigned faa (int n) // Ordinary recursion
{
return n<3 ? 1 :
faa(n-3)*faa(n-1) + 1; // Call, call, multiply, add
}
unsigned foo (int n) // Similar to tail recursion
{ // (reverse order)
int i;
unsigned f;
for (i=3, f=1; i<=n; i++)
f = f*foo(i-3) + 1;
return f;
}
unsigned fuu (int n) // Dynamic programming
{
int i;
unsigned A[4]={1,1,1,1};
for (i=3; i<=n; i++)
{
memmove (A+1, A, 3*sizeof(int));
A[0] = A[1]*A[3] + 1;
}
return A[0];
}
A[3] = A[2];
A[2] = A[1];
A[1] = A[0];