当我开始学习口齿不清时,我遇到了“尾部递归”这个词。这到底是什么意思?
当前回答
递归函数是一个自己调用的函数
它允许程序员用最少的代码编写高效的程序。
缺点是,如果编写不当,它们可能会导致无限循环和其他意外结果。
我将解释简单递归函数和尾部递归函数
为了编写简单的递归函数
首先要考虑的一点是你应该什么时候决定出来是if循环的第二个问题是,如果我们是自己的职能部门,我们应该做什么
从给定的示例中:
public static int fact(int n){
if(n <=1)
return 1;
else
return n * fact(n-1);
}
从上面的例子中
if(n <=1)
return 1;
是何时退出循环的决定因素
else
return n * fact(n-1);
是否要进行实际处理
为了便于理解,让我逐一完成任务。
让我们看看如果我运行事实(4),内部会发生什么
替换n=4
public static int fact(4){
if(4 <=1)
return 1;
else
return 4 * fact(4-1);
}
如果循环失败,则转到else循环因此它返回4*事实(3)
在堆栈内存中,我们有4*事实(3)替换n=3
public static int fact(3){
if(3 <=1)
return 1;
else
return 3 * fact(3-1);
}
如果循环失败,则转到else循环
因此它返回3*事实(2)
记住我们称之为“4*事实”(3)``
事实(3)的输出=3*事实(2)
到目前为止,堆栈具有4*事实(3)=4*3*事实(2)
在堆栈内存中,我们有4*3*事实(2)替换n=2
public static int fact(2){
if(2 <=1)
return 1;
else
return 2 * fact(2-1);
}
如果循环失败,则转到else循环
因此它返回2*事实(1)
记住我们称之为4*3*事实(2)
事实(2)的输出=2*事实(1)
到目前为止,堆栈具有4*3*事实(2)=4*3*2*事实(1)
在堆栈内存中,我们有4*3*2*事实(1)替换n=1
public static int fact(1){
if(1 <=1)
return 1;
else
return 1 * fact(1-1);
}
如果循环为真
所以它返回1
记住我们称之为4*3*2*事实(1)
事实(1)的输出=1
到目前为止,堆栈具有4*3*2*事实(1)=4*3*2*1
最后,事实(4)的结果=4*3*2*1=24
尾部递归将是
public static int fact(x, running_total=1) {
if (x==1) {
return running_total;
} else {
return fact(x-1, running_total*x);
}
}
替换n=4
public static int fact(4, running_total=1) {
if (x==1) {
return running_total;
} else {
return fact(4-1, running_total*4);
}
}
如果循环失败,则转到else循环因此它返回事实(3,4)
在堆栈内存中,我们有事实(3,4)替换n=3
public static int fact(3, running_total=4) {
if (x==1) {
return running_total;
} else {
return fact(3-1, 4*3);
}
}
如果循环失败,则转到else循环
因此它返回事实(2,12)
在堆栈内存中,我们有事实(2,12)替换n=2
public static int fact(2, running_total=12) {
if (x==1) {
return running_total;
} else {
return fact(2-1, 12*2);
}
}
如果循环失败,则转到else循环
因此它返回事实(1,24)
在堆栈内存中,我们有事实(1,24)替换n=1
public static int fact(1, running_total=24) {
if (x==1) {
return running_total;
} else {
return fact(1-1, 24*1);
}
}
如果循环为真
因此它返回running_total
running_total=24的输出
最后,事实(4,1)的结果=24
其他回答
这里有一个例子,而不是用文字来解释。这是阶乘函数的Scheme版本:
(define (factorial x)
(if (= x 0) 1
(* x (factorial (- x 1)))))
下面是一个阶乘的尾部递归版本:
(define factorial
(letrec ((fact (lambda (x accum)
(if (= x 0) accum
(fact (- x 1) (* accum x))))))
(lambda (x)
(fact x 1))))
在第一个版本中,您会注意到对事实的递归调用被馈送到乘法表达式中,因此在进行递归调用时,状态必须保存在堆栈中。在尾部递归版本中,没有其他S表达式等待递归调用的值,并且由于没有进一步的工作要做,状态不必保存在堆栈上。通常,Scheme尾部递归函数使用常数堆栈空间。
使用常规递归,每个递归调用将另一个条目推送到调用堆栈中。递归完成后,应用程序必须将每个条目向下弹出。
使用尾部递归,根据语言的不同,编译器可以将堆栈折叠为一个条目,这样可以节省堆栈空间。。。大型递归查询实际上会导致堆栈溢出。
基本上,尾部递归可以优化到迭代中。
在Java中,以下是斐波那契函数的一个可能的尾部递归实现:
public int tailRecursive(final int n) {
if (n <= 2)
return 1;
return tailRecursiveAux(n, 1, 1);
}
private int tailRecursiveAux(int n, int iter, int acc) {
if (iter == n)
return acc;
return tailRecursiveAux(n, ++iter, acc + iter);
}
与标准递归实现形成对比:
public int recursive(final int n) {
if (n <= 2)
return 1;
return recursive(n - 1) + recursive(n - 2);
}
这里是前面提到的tailrecsum函数的Perl 5版本。
sub tail_rec_sum($;$){
my( $x,$running_total ) = (@_,0);
return $running_total unless $x;
@_ = ($x-1,$running_total+$x);
goto &tail_rec_sum; # throw away current stack frame
}
简而言之,尾部递归将递归调用作为函数中的最后一条语句,这样就不必等待递归调用。
所以这是一个尾部递归,即N(x-1,p*x)是函数中的最后一个语句,编译器聪明地发现它可以优化为for循环(阶乘)。第二个参数p携带中间乘积值。
function N(x, p) {
return x == 1 ? p : N(x - 1, p * x);
}
这是编写上述阶乘函数的非尾部递归方式(尽管某些C++编译器可能无论如何都能优化它)。
function N(x) {
return x == 1 ? 1 : x * N(x - 1);
}
但这不是:
function F(x) {
if (x == 1) return 0;
if (x == 2) return 1;
return F(x - 1) + F(x - 2);
}
我确实写了一篇题为“理解尾部递归——Visual Studio C++——汇编视图”的长文
推荐文章
- 有效的方法应用多个过滤器的熊猫数据框架或系列
- 什么是ORM,它是如何工作的,我应该如何使用它?
- 我能在服务器端应用程序(PHP、Ruby、Python等)上读取URL的哈希部分吗?
- 多少个参数是太多?
- 对于没有null的语言的最佳解释
- Python:为什么functools。部分有必要吗?
- 哪些是遗传算法/遗传规划解决方案的好例子?
- 如何在O(n)中找到长度为n的无序数组中的第k大元素?
- 匿名递归PHP函数
- 一个用于膨胀/收缩(抵消,缓冲)多边形的算法
- 我如何使用Node.js Crypto创建HMAC-SHA1哈希?
- 对于不可变集合上的非突变“add”方法,最好的名称是什么?
- 如何用动态规划确定最长递增子序列?
- 在c++程序中以编程方式检测字节序
- 理解递归