使用java实现斐波那契数列的3中方式
public class Demo {
public static void main(String[] args) {
// num = 10 、20 、50 时 测试斐波那契函数耗时和计算值
testFibonacci(10);
System.out.println("--------------");
testFibonacci(20);
System.out.println("--------------");
testFibonacci(50);
System.out.println("--------------");
}
public static void testFibonacci(int num){
// 第50 个 斐波那契数
System.out.println("num = "+ num);
long start = System.nanoTime();
long val = Fibonacci(num);
long end = System.nanoTime();
System.out.println("Fibonacci 耗时:" +(end - start)+",值="+val);
start = System.nanoTime();
val = Fibonacci2(num,1,1);
end = System.nanoTime();
System.out.println("Fibonacci2 耗时:" +(end - start)+",值="+val);
start = System.nanoTime();
val = Fibonacci3(num);
end = System.nanoTime();
System.out.println("Fibonacci3 耗时:" +(end - start)+",值="+val);
}
/** 斐波那契 非尾调用*/
public static long Fibonacci(long n){
if ( n <= 1 ) {return 1;}
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
/** 斐波那契 尾调用*/
public static long Fibonacci2(int n,long num1 ,long num2 ){
if ( n <= 1 ) {return num2;}
return Fibonacci2 (n - 1, num2, num1 + num2);
}
/** 斐波那契 for*/
public static long Fibonacci3(int n){
if ( n <= 1 ) {return 1;}
long num1 = 1;
long num2 = 1;
long val = 0;
for (int i = 2; i <= n; i++) {
val = num1+num2;
num1 = num2;
num2 = val;
}
return val;
}
}
执行main函数,控制台打印的信息来看,随着num的数值越大,非尾调用方式执行的时间越长,比对另外2种方式时间差距巨大,而尾调用和for循环执行的时间相差不大。所以java函数尾递归调用是明显存在的。
尾递归
函数调用自身,称为递归。如果尾调用自身,就称为尾递归。
递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。
尾调用优化
尾调用之所以与其他调用不同,就在于它的特殊的调用位置。
我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A
的内部调用函数B
,那么在A
的调用帧上方,还会形成一个B
的调用帧。等到B
运行结束,将结果返回到A
,B
的调用帧才会消失。如果函数B
内部还调用函数C
,那就还有一个C
的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。
尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。