A类递归的在函数的“末尾”调用函数尾部递归.

更正式地说,调用方会立即返回尾部递归调用的值。

尾部递归调用总是可以优化为简单的跳转——它们根本不是递归的!这是该语言使用的方案,它使用尾部递归来表示迭代。

T型 =T= 通话模式

尾部递归n.(名词)。

如果你还没有厌倦,看看尾部递归.

--术语档案,由自动节点雷斯克.

尾部递归是正常的一种特殊情况递归其中环境堆栈框架与例程的每个实例相关联的函数可以通过例程末尾的递归调用重新使用(因为这是父例程要做的最后一件事)。

首先,非尾递归(inC类):

int阶乘(int n){如果(n<=1)返回1;返回n*阶乘(n-1);}

注意,阶乘例程需要“记住”n的值,以便在递归调用返回时可以乘以它。

现在,一个真正的尾部递归版本:

int factorial_tail(int n,int累加器){如果(n<=1)回流蓄能器;返回factorial_tail(n-1,累加器*n);}int阶乘(int n){返回factorial_tail(n,1);}

注意,factorial_tail在执行递归调用时已经完成了所有相关的计算,因此它可以丢弃自己的n和accumulator值。

这让我们找到了关键转型这表明尾部递归相当于一个标准虽然():

int factorial_goto(int n,int累加器){开始时间:如果(n<=1)回流蓄能器;累加器*=n;n-=1;转到开始;}

一个人应该说服自己factorial_tail和factorial_goto在行为上是相同的;从那里,可以很容易地转换转到到while循环:

int factorial_while(int n,int累加器){而(n>1){累加器*=n;n-=1;}回流蓄能器;}
这是迭代递归在中进行了讨论计算机科学类。一致性实施方案的方言LISP公司必须根据此转换实现尾部递归。

尾部递归是一种更一般的转换的特例,称为尾箱优化; 基本上,任何C类以模式结尾的函数返回foo(args);可以转换为转到而不是引入额外的堆栈框架。

有趣的是,没有尾部递归,函数式编程失去了它的大部分魅力。纯函数语言需要递归表达了许多常见的结构。考虑一个典型的伪-C类事件处理循环:

用于(;;){事件=waitForEvent();/*对活动做点什么*/}

现在,写无限循环的方法函数式编程是通过递归(有些不准确的例子):

函数handleEvent(事件):(做些事情)句柄事件(getNextEvent())

现在,你可能还记得,每个函数调用需要一些记忆。请注意,此循环将需要更多内存用于它处理的每个事件;这在大多数递归算法上可能是可以接受的,但在没有尾部递归优化最终将运行内存不足.

尾部递归在编程在里面方案(…的方言LISP公司). 我不知道其他LISP公司变体,所以我不能代表它们说话。

因为写作的复杂性递归函数s、 他们最终往往是深递归的回想一下那个尾部递归算法s通常定义为价值递归调用相当于价值整个的功能。因此,那些在递归调用在将其传递给堆栈深层递归以经典的阶乘的 算法:

(定义阶乘(λ(n)(如果(=n 0)1(*n(阶乘(-n 1))))

请注意递归调用然后乘以n。如果你画出堆栈,这个算法每次你都变得越来越复杂递归,在算法到达基本情况,它仍然需要将该值乘以n的所有前面的值。这是一个很大的损失效率当您将其与以下内容进行比较时:

(定义阶乘(λ(n个答案)(如果(=n 0)回答(阶乘(-n 1)(*答案n)))

虽然现在有一个额外的变量,这是正确的尾部递归,因为一旦我们到达基本情况然后返回答案变量,就完成了。每个前一个递归调用只将它收到的值传递回堆栈。

如果这还不够方案 口译译员足够聪明,能够识别正确的尾部递归,并在计算时将其转换为实际迭代。这可能会在效率,以及减少记忆需要评估这些算法并大大减少了记忆需要使用。


资料来源:教授理查德·索尔特属于欧柏林学院

登录登记在这里写点什么或联系作者。