斐波那契數列3種解法(樸素遞迴、動態規劃、數學歸納)及演算法分析
本文來自網易公開課的<演算法導論>第3講分治法。讓我對分治法的使用有了一個新的認識斐波那契數列,又稱黃金分割數列,F0=0,F1=1,Fn=F(n-1)+F(n-2)(n>=2,n∈N*)
下面我將使用Java(是的,又是Java,不過我覺得沒什麼問題,演算法嘛,重在思想)來分別實現這三種方法。後來視訊看到一半,發現使用樸素遞迴方法求解該問題時,有許多重複的子問題,那麼這就符合動態規劃的基本思想了,可以將採用自底向上的求解順序,儲存子問題的結果。這樣做的演算法時間是:theta(n)
1.樸素遞迴:自定向向下求解問題,導致了大量的重複求解
2.動態規劃:準確的講應該是一種自底向上的"動態規劃"思想解法。
3.數學歸納法(線性代數矩陣連乘公式)
設Fn表示第n個斐波那契數,那麼有定理:
證明:當n=1,F0=0,F1=1,F2=2,即:
那麼假設定理成立,將n-1帶入可得表示式:
即:
因為等式
恆成立,於是假設成立。
這樣就將斐波那契數列轉換成了n乘方問題了,那麼n乘方問題的演算法時間為什麼不是,而是呢?這裡可以使用分治法求解n乘方問題,可將n個數連乘分成左右相等的兩部分(偶數d的話n/2和n/2,奇數的話(n-1)/2和(n-1)/2)。就有:
這樣n個數連乘,只需劃分次即可。
程式碼如下:
執行結果:package com.wly.algorithmproblem; /** * 解斐波拉契數列問題,使用三種方法:樸素遞迴解法、自底向上的動態規劃思想解法、線性代數矩陣連乘公式解法 * @author wly * @date 2013-11-28 * */ public class FibonacciSequence { private static int TESTCASE = 43; private static int[][] matrixUnit = {{1,1},{1,0}}; public static void main(String[] args) { System.out.println("測試規模:" + TESTCASE); //---樸素遞迴解斐波那契數列問題測試 long startT = System.currentTimeMillis(); System.out.println("樸素遞迴:" + simpleRecurrence(TESTCASE)); System.out.println("樸素遞迴用時:" + (System.currentTimeMillis()-startT)); //---自底向上(動態規劃)解斐波那契數列問題測試 startT = System.currentTimeMillis(); System.out.println("自底向上(動態規劃):" + downToTopReslove(TESTCASE)); System.out.println("自底向上(動態規劃)用時:" + (System.currentTimeMillis()-startT)); //---線性代數矩陣解斐波那契數列問題測試 int[][] mResult = {{1,1},{1,0}}; startT = System.currentTimeMillis(); int n = 1; while(n<TESTCASE) { mResult = matrixMutiple(mResult, matrixUnit); n ++; } System.out.println("線性代數矩陣公式:" + mResult[0][1]); System.out.println("線性代數矩陣公式用時:" + (System.currentTimeMillis()-startT)); //分治法求m的n連乘測試 System.out.println("分治法求2的23連乘:" + pow(2, 23)); //兩矩陣相乘方法測試 /* int[][] matrix1 = {{2,3,4},{1,2,3}}; int[][] matrix2 = {{2,4},{3,5},{4,6}}; int[][] result = new int[matrix1.length][matrix2[0].length]; int[][] resultS = matrixMutiple(matrix1,matrix2,result); System.out.println(); */ } /** * 樸素遞迴 * @param n * @return 第n個斐波那契數 */ public static int simpleRecurrence(int n) { if(n == 0) { return 0; } if(n == 1 || n == 2) { return 1; } return simpleRecurrence(n-1) + simpleRecurrence(n-2); } /** * 自底向上包含"動態規劃"思想的解法 * @param n * @return 第n個斐波那契數 */ public static int downToTopReslove(int n) { if(n == 0) { return 0; } else if(n == 1 || n == 2) { return 1; } else { int[] fibonacciArray = new int[n+1]; //fibonacciArray[i]表示第i個斐波那契數 fibonacciArray[0] = 0; fibonacciArray[1] = 1; fibonacciArray[2] = 1; for(int i=3;i<=n;i++) { //注意由於fibonacciArray[0]表示第0個元素,這裡是i<=n,而不是i<n fibonacciArray[i] = fibonacciArray[i-1] + fibonacciArray[i-2]; } return fibonacciArray[fibonacciArray.length-1]; } } /** * 分治法求解factor的n次方 * @param factor 基數 * @param n 次方數 * @return */ public static long pow(long factor,int n) { if(n == 0) { return 1; } else if(n == 1){ return factor; } else { if(n % 2 == 1) { //乘法數為奇數 return pow(factor,(n-1)/2) * pow(factor, (n-1)/2) * factor; } else { //乘方數為偶數 return pow(factor, n/2) * pow(factor, n/2); } } } /** * 兩矩陣相乘 * @param matrix1 * @param matrix2 * @return */ public static int[][] matrixMutiple(int[][] matrix1,int[][] matrix2) { int[][] result = new int[matrix1.length][matrix2[0].length]; for(int i=0;i<matrix1.length;i++) { for(int j=0;j<matrix2[i].length;j++) { int temp = 0; for(int k=0;k<matrix1[0].length;k++) { temp = matrix1[i][k] * matrix2[k][j] + temp; } result[i][j] = temp; } } return result; } }
測試規模:43
樸素遞迴:433494437
樸素遞迴用時:1669
自底向上(動態規劃):433494437
自底向上(動態規劃)用時:0
線性代數矩陣公式:433494437
線性代數矩陣公式用時:1
分治法求2的23連乘:8388608
這裡有一點需要說明的是,程式碼中已經包含了m的n次方的程式碼。本來想講它結合到第三種線性代數矩陣連乘的解法中的,但是後來發現那樣的話,程式碼顯得很亂,於是就只是簡單的使用while來連乘了n次實現。總的來說還是實現了三種不同的解斐波拉契數列的方法,希望能夠大家一點參考
O啦~~~
轉載請保留出處:
謝謝!!