首页 > 编程知识 正文

消除尾递归,单向递归和尾递归迭代

时间:2023-05-03 20:38:41 阅读:153370 作者:3633

传统的递归中中,典型的模型是首先执行递归调用,然后获取递归调用的返回值并计算结果。 使用此方法,每次返回递归调用时都不会得到计算结果。 传统的递归过程是函数调用,涉及返回地址、函数参数和寄存器值等堆栈。 在x86-64中,函数参数通常保存在寄存器中。 这样做有两个缺点。

如果效率低下,占用内存的递归链过长,statck overflow函数有可能在末尾位置调用自身,或者末尾调用自身的其他函数等,将这种情况称为尾递归尾递归也是递归的特殊情况。 尾部递归是在尾部直接调用自身递归函数的特殊尾部调用。 末尾递归的优化也是关注末尾调用的主要原因。 尾部调用不一定是递归调用,但尾部递归特别有用,比较容易实现。

当编译器检测到3358 www.Sina.com/http://www.Sina.com /函数调用是尾部递归时,它将复盖当前活动日志,而不是在堆栈中创建新的。 编译器可以做到这一点。 递归调用是当前活动时段内最后一次执行的语句,因此返回此调用时,栈帧无法执行其他操作,因此不需要保存栈帧。 覆盖当前堆栈帧,而不是在其上新添加,大大减少了使用的堆栈空间,提高了实际操作效率。

特点:尾部递归除了普通的尾部调用外,还多具有两个特点:

在末尾调用的是函数本身(Self-called ); 优化可以使得计算只占用常数堆栈区域(Stack Space )。 描述:传统模式的编译器处理尾部调用的方式与处理其他常规函数调用的方式相同。 始终在调用时创建新的堆栈帧“stack frame”,并将其推送到调用堆栈的顶部以表示其子函数调用。

当调用一个函数时,计算机必须“记住”被调用函数的位置——返回的位置,才能在调用结束时带着返回值返回该位置。 返回位置通常存在于调用堆栈上。 在末尾调用这样的特殊情况下,计算机理论上不记住末尾调用的位置,就可以直接从被调用函数带着返回值返回调用函数的返回位置(相当于直接连续返回两次)。 取消尾部调用是一种优化,可以在不更改当前调用栈或不添加新返回位置的情况下跳转到新函数。 完全不更改调用堆栈是不可能的。 还是需要修改调用栈上的格式参数和局部变量信息。 )

大多数情况下都不需要当前函数框架,例如包含局部变量,因此,在适当修改当前函数框架后,可以将其直接用作在末尾调用的函数的框架。 然后,程序可以跳转到在末尾调用的函数。 消除尾部调用或优化尾部调用)生成这种函数帧更改代码和“跳转”而不是常见函数调用的代码的过程末尾调用的优化使位于末尾的函数调用和goto语句具有同样高的性能,因此高效的结构编程变得现实。

然而,在c等语言中,在函数的末尾使用ReturnG(X ); 很可能在尾部递归——返回之前参与对象的析构函数,并且g(x )不一定是最后一次被执行。 这可以通过优化返回值来解决。

尾递归的原理:中,首先执行计算,然后执行递归调用,将当前步骤的结果传递给下一个递归步骤。 因此,最后一条语句的形式为接收函数参数(return )。

考虑一下关于n的最基本的求和函数。 例如,sum(5)=1 2 3 4 5=15。

这是使用JavaScript实现的递归函数。

functionrecsum(x ) if ) x===1) { return x; }else{returnxrecsum(x-1; }调用}recsum(5)时,将按以下顺序计算JavaScript解释器:

recsum(5)5recsum )4)5)4recsum )3)5)3)3recsum )2) ) )5)5)4)3recsum )1) )

这是同一函数的尾部递归版本:

functiontailrecsum(x,running_total=0) if ) x===0) { return running_total; }else{returnTailrecsum(x-1,running_total x ); }以下是xldmj调用tailrecsum(5)时的实际事件调用顺序:

tailrecsum (5,0 ) tailrecsum ) 4,5 ) tailrecsum ) 3,9 ) tailrecsum ) 2,12 ) tailrecsum ) 1,14 ) tailrecsum ) 0,15

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。