一个对自己本身的递归尾调用,就叫做尾递归。这里尾调用的“尾”字,是指运行时需要执行的最后一个动作。不是简单的语法字面上的最后一个语句。 尾递归实际执行的是迭代的计算过程。线性递归函数必须满足以下两个基本属性:
*必须清晰无误地解决基的情况。
*每一个递归的调用,必须包含更小的参数值。
而尾递归则不必满足这两个条件。
普通的线性递归比尾递归更加消耗资源, 在实现上说, 每次重复的过程调用都使得调用链条不断加长. 系统不得不使用栈进行数据保存和恢复.而尾递归就不存在这样的问题, 因为它的状态完全由函数的参数保存. 并且,由于尾递归的函数调用出现在调用者函数的尾部,因为是尾部,所以根本没有必要去保存任何局部变量。直接让被调用的函数返回时越过调用者,返回到调用者的调用者去。尾调用优化不是什么很复杂的优化,实际上几乎所有的现代的高级语言编译器都支持尾调用这个很基本的优化。 实现层面上,只需要把汇编代码call改成jmp, 并放弃所有局部变量压栈处理,就可以了。这样一来,堆栈根本就没有被占用,每次调用都是重新使用调用者的堆栈。尽管尾递归比递归高效,但并非所有的递归算法都可以转成尾递归的,因为尾递归本质上执行的是迭代的计算过程。这与并非所有的递归算法都可以转成迭代算法的原因是一样的。
例子:java实现
public class Recursion {
public static void main(String[] args) {
System.out.println(factorial(6));
System.out.println(factorial2(6, 1));
System.out.println(reciprocal(3.0));
System.out.println(reciprocal2(3.0, 1));
}
// 递归 求 N 的阶乘
private static int factorial(int n) {
if (n == 0 || n == 1) {
return 1;
} else if (n < 0) {
return 0;
} else {
return n * factorial(n - 1);
}
}
// 尾递归 求 N 的阶乘
private static int factorial2(int n, int result) {
if (n == 0 || n == 1) {
return result;
} else if (n < 0) {
return 0;
} else {
return factorial2(n - 1, result * n);
}
}
// 递归求 1+1/2+1/3+1/4+......+n/1
private static double reciprocal(double n) {
if (n < 0) {
return 0;
} else if (n == 1) {
return 1;
} else {
return 1.0 / n + reciprocal(n - 1);
}
}
// 尾递归 求 1+1/2+1/3+1/4+......+1/n
private static double reciprocal2(double n, double result) {
if (n < 0) {
return 0;
} else if (n == 1) {
return result;
} else {
return reciprocal2(n - 1, result + 1.0 / n);
}
}
}