原本以为比较简单,但是后来发现,加能使用动态规划,算法更好。
1.题目
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。n<=39。
2.分析递归方法
1 public int fib0(int n)2 {3 if(n<=0)4 return 0;5 if(n==1)6 return 1;7 return fib( n-1)+fib(n-2);8 }
假设输入的是6:
上面的递归树中的每一个子节点都会执行一次,很多重复的节点被执行,fib(2)被重复执行了5次。由于调用每一个函数的时候都要保留上下文,所以空间上开销也不小。这么多的子节点被重复执行,如果在执行的时候把执行过的子节点保存起来,后面要用到的时候直接查表调用的话可以节约大量的时间。下面就看看动态规划的两种方法怎样来解决斐波拉契数列Fibonacci 数列问题。
3.使用动态规划
①自顶向下的备忘录法
1 public static int Fibonacci(int n) 2 { 3 if(n<=0) 4 return n; 5 int [] Memo=new int[n+1]; 6 for(int i=0;i<=n;i++) 7 Memo[i]=-1; 8 return fib(n, Memo); 9 }10 public static int fib(int n,int []Memo)11 {12 if(Memo[n]!=-1)13 return Memo[n];14 //如果已经求出了fib(n)的值直接返回,否则将求出的值保存在Memo备忘录中。15 if(n<=2)16 Memo[n]=1;17 else Memo[n]=fib( n-1,Memo)+fib(n-2,Memo);18 return Memo[n];19 }
备忘录法也是比较好理解的,创建了一个n+1大小的数组来保存求出的斐波拉契数列中的每一个值,在递归的时候如果发现前面fib(n)的值计算出来了就不再计算,如果未计算出来,则计算出来后保存在Memo数组中,下次在调用fib(n)的时候就不会重新递归了。比如上面的递归树中在计算fib(6)的时候先计算fib(5),调用fib(5)算出了fib(4)后,fib(6)再调用fib(4)就不会在递归fib(4)的子树了,因为fib(4)的值已经保存在Memo[4]中。
②自底向上的动态规划
备忘录法还是利用了递归,上面算法不管怎样,计算fib(6)的时候最后还是要计算出fib(1),fib(2),fib(3)……,那么何不先计算出fib(1),fib(2),fib(3)……,呢?这也就是动态规划的核心,先计算子问题,再由子问题计算父问题。
1 public static int fib(int n) 2 { 3 if(n<=0) 4 return n; 5 int []Memo=new int[n+1]; 6 Memo[0]=0; 7 Memo[1]=1; 8 for(int i=2;i<=n;i++) 9 {10 Memo[i]=Memo[i-1]+Memo[i-2];11 }12 return Memo[n];13 }
③优化
自底向上方法也是利用数组保存了先计算的值,为后面的调用服务。观察参与循环的只有 i,i-1 , i-2三项,因此该方法的空间可以进一步的压缩如下。
1 public static int fib3(int n) 2 { 3 if(n<=1) 4 return n; 5 6 int Memo_i_2=0; 7 int Memo_i_1=1; 8 int Memo_i=1; 9 for(int i=2;i<=n;i++)10 {11 Memo_i=Memo_i_2+Memo_i_1;12 Memo_i_2=Memo_i_1;13 Memo_i_1=Memo_i;14 }15 return Memo_i;16 }
4.完整的程序
1 package first; 2 3 public class Fibonacci { 4 public static void main(String[] args){ 5 // 6 int a=Fibonacci(3); 7 System.out.println("a="+a); 8 // 9 int b=fib(3);10 System.out.println("b="+b);11 }12 13 public int fib0(int n)14 {15 if(n<=0)16 return 0;17 if(n==1)18 return 1;19 return fib( n-1)+fib(n-2);20 }21 22 23 public static int Fibonacci(int n)24 {25 if(n<=0)26 return n;27 int [] Memo=new int[n+1];28 for(int i=0;i<=n;i++)29 Memo[i]=-1;30 return fib(n, Memo);31 }32 public static int fib(int n,int []Memo)33 {34 if(Memo[n]!=-1)35 return Memo[n];36 //如果已经求出了fib(n)的值直接返回,否则将求出的值保存在Memo备忘录中。37 if(n<=2)38 Memo[n]=1;39 else Memo[n]=fib( n-1,Memo)+fib(n-2,Memo);40 return Memo[n];41 }42 43 public static int fib(int n)44 {45 if(n<=0)46 return n;47 int []Memo=new int[n+1];48 Memo[0]=0;49 Memo[1]=1;50 for(int i=2;i<=n;i++)51 {52 Memo[i]=Memo[i-1]+Memo[i-2];53 }54 return Memo[n];55 }56 57 public static int fib3(int n)58 {59 if(n<=1)60 return n;61 62 int Memo_i_2=0;63 int Memo_i_1=1;64 int Memo_i=1;65 for(int i=2;i<=n;i++)66 {67 Memo_i=Memo_i_2+Memo_i_1;68 Memo_i_2=Memo_i_1;69 Memo_i_1=Memo_i;70 }71 return Memo_i;72 }73 74 75 76 }