從斐波那契數列窺探動態規劃
利用動態規劃求解斐波那契數列
結果如下:public class Fibonacci { static int f[]=new int[100]; static long startTime=0; public static void init() { for(int i=0;i<f.length;i++) f[i]=-1; } public static void main(String[] args) { init(); startTime=System.currentTimeMillis(); fibonacci(40); System.out.println("time:"+(System.currentTimeMillis()-startTime)); fibonacci2(40); startTime=System.currentTimeMillis(); System.out.println(fibonacci3(40)); System.out.println("time:"+(System.currentTimeMillis()-startTime)); } static int fibonacci(int i) //遞迴是一種自上而下的動態規劃。 { if(i==0) { return 0; } else if(i==1) { return 1; } else { return fibonacci(i-1)+fibonacci(i-2); } } static int fibonacci2(int n) //一般的動態規劃,就是這種自下而上的動態規劃 { int[] array=new int[n+1]; array[0]=0; array[1]=1; long startTime=System.currentTimeMillis(); for(int i=2;i<n+1;i++){ array[i]=array[i-1]+array[i-2]; } for(int i=1;i<n+1;i++){ System.out.print(array[i]+" "); } System.out.println(); System.out.println("time:"+(System.currentTimeMillis()-startTime)); return array[40]; } static int fibonacci3(int n) //備忘錄法,跟自頂向下的動態規劃遞迴是一樣的,不同的是備忘錄法利用了一個數組來記錄每個子問題的解,從而避免重複求解,將問題簡化。 { if(f[n]>=0) return f[n]; if(n == 0) { f[0] = 0; return f[0]; } if(n == 1) { f[1] = 1; return f[1]; } f[n] = fibonacci3(n-1) + fibonacci3(n-2); return f[n]; } }
time:545
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 14930352 24157817 39088169 63245986 102334155
time:0
102334155
time:0
相比較:備忘錄法和自下向上的動態規劃效率差不多,而自頂向下的遞迴則效率很慢。是以空間換時間的做法。
動態規劃分為三種:自上而下有兩種,備忘錄法和遞迴。自下而上有一種,就是一般我們所使用的。
而備忘錄和遞迴不同,備忘錄法利用了一個額外陣列來儲存,計算過程中子問題的解,從而避免了遞迴方法中重複求解子問題的問題。
除了以上幾種方法外還有求通項公式的方法直接得出F(n)=(1/√5)*{[(1+√5)/2]^n - [(1-√5)/2]^n}。求解更快速,但這是利用數學的方法,程式設計時不支援這樣做。
結論:
動態規劃求解的問題的一般要具有3個性質:
(1) 最優化原理:如果問題的最優解所包含的子問題的解也是最優的,就稱該問題具有最優子結構,即滿足最優化原理。
(2) 無後效性:即某階段狀態一旦確定,就不受這個狀態以後決策的影響。也就是說,某狀態以後的過程不會影響以前的狀態,只與當前狀態有關。
(3) 有重疊子問題:即子問題之間是不獨立的,一個子問題在下一階段決策中可能被多次使用到。(該性質並不是動態規劃適用的必要條件,但是如果沒有這條性質,動態規劃演算法同其他演算法相比就不具備優勢)