代码在最后面
第一种:普通递归
代码写起来最简单,但是性能很差,容易爆栈
从图中也可以看出,存在很多次的重复计算,所以我们自然会想到针对这一问题进行改进
第二种:自顶向下的备忘录法
从图中可以看出,这一趟递归就已经完成了所有子问题的计算,所以,在这里我们的思路就是在这一趟中就把子问题的解存起来,下次用的时候就不需要再计算了
但是毕竟每次递归的时候还需要存储上下文,所以性能也不是太好,一般牵扯到递归的时候性能都好不到哪去
第三种:自底向上的备忘录法
这个方法就舍弃了递归了,具体实现就是搞一个数组,从n=3开始,一个一个的算,算完就存起来,每次的计算就是取n-1和n-2的值加起来。
第四种:优化了空间复杂度的自底向上的备忘录法
在自底向上的备忘录法中,可以看到每次计算的时候只用到了n-1和n-2的值,所以,其实不需要一个长度n的数组,只需要两个变量来存N-1和N-2,以及一个变量来存当前的计算结果就可以了。
这也是这五种方法中用时最短的方法。
第五种:尾递归
尾递归有点像是第四种方法与递归的结合
enum Methods {
recursion, memoFromTop, memoFromBottom, memoFromBottomV2, tailRecursion
}
public class Fibonacci {
void testMethods(Methods method, int n) {
//ms
// long start = System.currentTimeMillis();
long start = System.nanoTime();
long result = -1;
switch (method) {
case recursion:
result = recursion(n);
break;
case memoFromTop:
result = memoFromTop(n);
break;
case memoFromBottom:
result = memoFromBottom(n);
break;
case memoFromBottomV2:
result = memoFromBottomV2(n);
break;
case tailRecursion:
result = tailRecursion(n, 0, 1);
break;
}
//long end = System.currentTimeMillis();
long end = System.nanoTime();
//System.out.println(method+":"+(end - start)+"ms");
System.out.println(method + ":" + (end - start) + "ns");
System.out.println(result);
System.out.println("----------------");
}
//1. 普通递归
long recursion(int n) {
if (n <= 0)
return 1;
if (n > 2)
return recursion(n - 1) + recursion(n - 2);
return 1;
}
//2. 自顶向下memo
long memoFromTop(int n) {
if (n < 1) {
return 0;
}
long[] memo = new long[n + 1];//让数组的索引与数列索引同步起来所以用的n+1
for (int i = 0; i < n + 1; i++) {
memo[i] = -1;
}
return getFromTopMemo(n, memo);
}
private long getFromTopMemo(int n, long[] memo) {
if (memo[n] != -1) {
return memo[n];
}
if (n <= 2) {
memo[n] = 1;
} else {
memo[n] = getFromTopMemo(n - 1, memo) + getFromTopMemo(n - 2, memo);
}
return memo[n];
}
//3. 自底向上memo
long memoFromBottom(int n) {
if (n < 1) {
return 0;
}
if (n == 1 || n == 2) {
return 1;
}
long[] memo = new long[n];
memo[0] = 1;
memo[1] = 1;
for (int i = 2; i < n; i++) {
memo[i] = memo[i - 1] + memo[i - 2];
}
return memo[n - 1];
}
//4. 减少空间复杂度的自底向上memo
long memoFromBottomV2(int n) {
if (n < 1) {
return 0;
}
if (n == 1 || n == 2) {
return 1;
}
long a = 1;
long b = 2;
long result = a + b;
for (int i = 3; i <= n; i++) {
a = b;
b = result;
result = a + b;
}
return result;
}
//5. 尾递归
//用递归代替了循环,用ret1和ret2代替了memoFromBottomV2中的两个变量
//可能在别的地方尾递归有一些独特的优势,但在这里,感觉这个算法除了花里胡哨外并没有什么特别的优势
//尾递归需要在编译器的配合下才能实现防止栈溢出,因为上层递归函数只是有了可以销毁的可能性,具体销毁与否得看编译器
long tailRecursion(int n, long ret1, long ret2) {
if (n == 0) {
return ret1;
}
return tailRecursion(n - 1, ret2, ret1 + ret2);
}
public static void main(String[] args) {
final int TEST_NUMBER = 45;
Fibonacci fibonacci = new Fibonacci();
fibonacci.testMethods(Methods.memoFromBottomV2, TEST_NUMBER);//memoFromBottomV2:2500ns
fibonacci.testMethods(Methods.memoFromBottom, TEST_NUMBER);//memoFromBottom:2701ns
fibonacci.testMethods(Methods.tailRecursion, TEST_NUMBER);//tailRecursion:2800ns
fibonacci.testMethods(Methods.memoFromTop, TEST_NUMBER);//memoFromTop:7100ns
fibonacci.testMethods(Methods.recursion, TEST_NUMBER);//recursion:2395126101ns
}
}