要说末尾递归,需要理解末尾调用
末尾调用定义
所谓维基百科的末尾调用,是指函数内最后的动作返回函数的调用结果的情况,在最后的步骤中新调用的返回值直接反映在当前函数的返回值中
在代码形式上
一个函数最后调用另一个函数
//只是举例,没有使用特定语言的语法
函数f (x ) {
a(x );
b(x );
返回(x; //在函数执行的末尾调用另一个函数
}
理解核心
就是看一个函数调用另一个函数时,它本身是否会“释放”
确定:以下呼叫的末尾呼叫
//情况1
是函数f (x )
{
inta=g(x;
return a;
}
//情况2
是函数f (x )
{
return3g(x;
}
//情况3
是函数f (x )
{
是if(x0 )
{
返回(x;
}
returnr(x;
}
答案
情况1在调用函数g(x )后有其他操作,因此不属于末尾调用。 即使意思相同,但a要得到结果,就需要等待g ) x )函数,所以f ) x )是不能释放的。
情况2不属于尾部调用,因为在调用之后还有另一个操作。 即使写在同一行,也同样不能释放f(x )。
在事例3中,无论x取什么样的值,最后的操作都是函数调用,所以属于末尾调用。
呼叫尾有什么好处
首先请看普通呼叫的过程
//用以下三个函数举例
是函数f (x )
{
RES=g(x;
返回结果1;
}
是函数g (x )
{
RES=r(x;
返回结果1;
}
函数r (x )表示
{
res=x 1;
返回结果1;
}
文本说明
函数调用
调用f(x )在内存中创建调用记录。 也称为调用帧(call frame ),用于保存调用位置和内部变量等信息。
在函数f(x )内调用函数g ) x )时,会在f ) x )的调用框上方形成g ) x )的调用框
在函数g(x )内部还调用函数r ) x ),因此在g ) x )的调用框的上方形成r ) x )的调用框
函数r(x )的调用结束,结果返回g ) x ),同时函数r ) x )结束,“消失”
同样,g(x )呼叫结束,“消失”
最后到f(x ),结束后消失(图中未出现) )。
在上述调用过程中,所有调用帧都位于一个调用堆栈中
以上步骤维基百科中的说明在程序运行时,计算机会为APP应用程序分配一定的内存空间APP应用程序会自行分配获取的内存空间。 其中一部分用于记录程序中调用的每个函数的执行情况。 这就是函数的调用堆栈。 在常规函数调用中,新的堆栈帧(也称为stack frame、“堆栈帧”或简称为“帧”)始终添加到调用堆栈的顶层。 此过程称为“堆栈入”或“堆栈”,意味着将新帧推到堆栈的顶层。
上述呼叫过程中有什么风险
如下图所示,函数调用级数过多时,调用栈会消耗大量内存,甚至会导致内存空间爆炸(栈溢出),导致程序严重卡顿或意外崩溃。
尾部呼叫解决了上述风险
//这是末尾调用
function f () }
m=1;
n=2;
返回(Mn );
}
f (;
与//相同
function f () }
返回(3;
}
f (;
与//相同
g(3);
上面的代码在我们调用g后,发现函数f与f没有任何关系,而是结束了。 因此,如果执行到最后一个步骤,就可以完全删除f () )的调用记录,只保留g ) )的调用记录。
末尾调用的含义
如果所有函数都是尾部调用,则每次执行都会有一个调用帧,从而大大节省内存
什么是末尾递归
末尾递归=末尾调用递归
递归:函数调用本身称为递归
末尾调用:函数最后调用另一个函数
因此,末尾递归可以概括为一个函数在其内部的最后一步调用自己
用python举例
定义记录(x,running_total=0) :
if x==0:
return running_total
else:
#tailrecsum函数的最后一步是调用另一个函数。 其中,这个“另一个函数”本身
returntailrecsum(x-1,running_total x ) ) ) ) ) )。
参考