递归与尾递归详解

递归与尾递归详解

下面两个程序是scheme(Lisp语言)写的计算阶乘的递归和尾递归实现

线性递归:

(define (factorial n)

(if (=n 1)

1

(* n (factorial (- n 1)))))

尾递归:

(define (factorial n)

(fact-iter 1 1 n))

(define (fact-iter product counter max-count)

(if (> counter max-count)

product

(fact-iter (* counter product)

(+ counter 1)

max-count)))

用C写出来就是这样的:

线性递归:

long factorial(long n)



return(n == 1) ? 1 : n * factorial(n - 1); 



尾递归:

long fact_iter(long product, long counter, long maxcount)



return (counter > maxcount) ? product : fact_iter(product*counter, counter+1, maxcount);



long factorial(long n)

{

return fact_iter(1, 1, n); 



线性递归程序基于阶乘的递归定义,即:对于一个正整数n,n!就等于n乘以(n-1)!:

n! = n[(n-1)(n-2)…3*2*1] =n(n-1)!

程序一在计算4!时的过程是这样的:

(factorial 4)

(* 4 (factorial 3))

(* 4 (* 3 (factorial 2)))

(* 4 (* 3 (* 2 (factorial 1))))

(* 4 (* 3 (* 2 1)))

(* 4 (* 3 2))

(* 4 6)

24

而尾递归程序用的是另一种计算规则,即先用1乘以2,再将得到的结果乘以3,再乘以4,这样下

去直到n。程序中维持着一个变动中的乘积product,以及一个从1到n的计数器counter,这一

计算过程中的product和counter在每一步都按照下面规则改变:

product = counter * product

counter = counter + 1

这样循环下去,可以得到n!也就是计数器counter超过n时乘积product的值。

所以程序二在计算4!的过程是这样的:

(factorial 4)

(fact-iter 1 1 4)

(fact-iter 1 2 4)

(fact-iter 2 3 4)

(fact-iter 6 4 4)

(fact-iter 24 5 4)

24

一个对自己本身的递归尾调用,就叫做尾递归。这里尾调用的“尾”字,是指运行

时需要执行的最后一个动作。不是简单的语法字面上的最后一个语句。 尾递归实

际执行的是迭代的计算过程。

线性递归函数必须满足以下两个基本属性:

*必须清晰无误地解决基的情况。

*每一个递归的调用,必须包含更小的参数值。

而尾递归则不必满足这两个条件。

普通的线性递归比尾递归更加消耗资源, 在实现上说, 每次重复的过程调用都使得调

用链条不断加长. 系统不得不使用栈进行数据保存和恢复.而尾递归就不存在这样的问

题, 因为它的状态完全由函数的参数保存. 并且,由于尾递归的函数调用出现在调用者

函数的尾部,因为是尾部,所以根本没有必要去保存任何局部变量,sp, pc的信息。

直接让被调用的函数返回时越过调用者,返回到调用者的调用者去。

尾调用优化不是什么很复杂的优化,实际上几乎所有的现代的高级语言编译器都支持

尾调用这个很基本的优化。 实现层面上,只需要把汇编代码call改成jmp, 并放弃所有

局部变量压栈处理,就可以了。这样一来,堆栈根本就没有被占用,每次调用都是重

新使用调用者的堆栈。

尽管尾递归比递归高效,但并非所有的递归算法都可以转成尾递归的,因为尾递归本质

上执行的是迭代的计算过程。这与并非所有的递归算法都可以转成迭代算法的原因是一样的。

参考来源:《计算机程序的构造和解释》

你可能感兴趣的:(计算机程序的构造和解释)