1. 程式人生 > 實用技巧 >用x種方式求第n項斐波那契數,99%的人只會第一種

用x種方式求第n項斐波那契數,99%的人只會第一種

大家好啊,我們又見面了。聽說有人想學資料結構與演算法卻不知道從何下手?那你就認真看完本篇文章,或許能從中找到方法與技巧。

    本期我們就從斐波那契數列的幾種解法入手,感受演算法的強大與奧妙吧。

原文連結:原文來自個人公眾號:C you again,歡迎關注

斐波那契數列

    斐波那契數列(Fibonacci sequence),又稱黃金分割數列,因數學家萊昂納多·斐波那契(Leonardoda Fibonacci)以兔子繁殖為例子而引入,故又稱為“兔子數列”。

    斐波那契數列指的是這樣一個數列:

0、1、1、2、3、5、8、13、21、34......

    有一組數列,它的第一項為1,第二項為1,從第三項開始,每一項為前兩項之和。

    斐波那契數列的第n項Fn可以通過如下的遞迴公式定義:

F(1)=1,F(2)=1,
F(n)=F(n-1)+F(n-2)(n ≥ 3,n ∈ N*)

通項公式


    如上,又稱為“比內公式”,是用無理數表示有理數的一個範例。

注:此時a1=1,a2=1,a(n)=a(n-1)+a(n-2),(n ≥ 3,n ∈ N*)

求第n項斐波那契數

    現在寫一個函式int fib(int n) 返回第n項Fn。例如,若n=0,則函式fib(0)應該返回0,若n=1, 則函式fib(1)應返回1,若 n > 1,返回 F(n-1)+F(n-2)。

若n = 9
輸出:34

    下面是返回斐波那契數列第n項Fn的不同方法:

方法1 (使用遞迴)

    一個簡捷的方法是直接使用遞迴定義關係式寫出遞迴實現的程式碼,C/C++程式碼如下:

//Fibonacci Series using Recursion
#include<stdio.h>
int fib(int n) {
    if (n <= 1)
        return n;
    return fib(n - 1) + fib(n - 2);
}

int main() {
    int n = 9;
    printf("%d", fib(n));

    return 0;
}

輸出:34
時間複雜度:T(n) = T(n-1) + T(n-2),該時間複雜度是指數級別的
空間複雜度:如果考慮遞迴呼叫時棧的大小,則為O(n) ;如果不考慮呼叫棧的話,則為O(1)

    通過觀察,我們可以發現遞迴求解時做了很多重複的工作(見下面的遞迴呼叫樹)。因此採用遞迴方式求解斐波那契數列的第n項Fn不是一種好的方法。

方法2 (使用動態規劃Dynamic Programming:DP)

    如果你還不瞭解動態規劃,請看以下兩篇文章:

    《深入淺出理解動態規劃(一) | 交疊子問題》

    《深入淺出理解動態規劃(二) | 最優子結構》

    在方法1中,在求解某項時,如果我們把計算結果儲存起來,則後續的計算就可以使用前面的計算結果,從而可以避免很多重複的計算,C/C++程式碼如下:

//Fibonacci Series using Dynamic Programming
#include<stdio.h>

int fib(int n) {
    /* Declare an array to store Fibonacci numbers. */
    int f[n + 1];
    int i;

    /* 0th and 1st number of the series are 0 and 1*/
    f[0] = 0;
    f[1] = 1;

    for (i = 2; i <= n; i++) {
        /* Add the previous 2 numbers in the series
         and store it */
        f[i] = f[i - 1] + f[i - 2];
    }

    return f[n];
}

int main() {
    int n = 9;
    printf("%d", fib(n));

    return 0;
}

輸出:34
時間複雜度:O(n)
空間複雜度: O(n)

方法3 (對方法2進行空間上的優化)

    由於在計算某項時只需其前面相鄰的兩項,因此可以對方法2中的空間進行優化,C/C++程式碼如下:

// Fibonacci Series using Space Optimized Method
#include<stdio.h>
int fib(int n) {
    int a = 0, b = 1, c, i;
    if (n == 0)
        return a;
    for (i = 2; i <= n; i++) {
        c = a + b;
        a = b;
        b = c;
    }
    return b;
}

int main() {
    int n = 9;
    printf("%d", fib(n));

    return 0;
}

輸出:34
時間複雜度: O(n)
空間複雜度: O(1)

    當然,也可以使用滾動陣列。滾動陣列不是什麼高大上的技術,我們在計算斐波那契數列的過程中,始終使用相鄰的前兩項,加上正在計算的項,總共就三項,因此可以定義一個長度只有3的陣列,可以滾動地使用0、1、2這三個下標。程式碼如下:

//Fibonacci Series using Dynamic Programming
#include<stdio.h>

int fib(int n) {
    /* Declare an array to store Fibonacci numbers. */
    int f[3];       /* 只需定義一個長度為3的陣列 */
    int i;

    /* 0th and 1st number of the series are 0 and 1*/
    f[0] = 0;   
    f[1] = 1;

    for (i = 2; i <= n; i++) {
        /* Add the previous 2 numbers in the series
         and store it:注意下標要對3取模 */
        f[i % 3] = f[(i - 1) % 3] + f[(i - 2) % 3];
    }

    return f[n % 3]; /* 這裡要注意下標對3取模 */
}

int main() {
    int n = 9;
    printf("%d", fib(n));

    return 0;
}

方法4 (使用矩陣{{1,1},{1,0}}的冪)

    另外一種複雜度為O(n)的方法是對矩陣M={{1,1},{1,0}}自乘n次(換句話說,就是計算矩陣M的n次冪:power(M,n)), 這樣就可以在結果矩陣下標為(0, 0)的地方得到斐波那契數列的第(n+1)項,如下所示:

#include <stdio.h>

/* Helper function that multiplies 2 matrices F and M of size 2*2, and puts the multiplication result back to F[][] */
void multiply(int F[2][2], int M[2][2]);

/* Helper function that calculates F[][] raise to the power n and puts the result in F[][]。Note that this function is designed only for fib() and won't work as general power function */
void power(int F[2][2], int n);

int fib(int n) {
    int F[2][2] = { { 1, 1 }, { 1, 0 } };
    if (n == 0)
        return 0;
    power(F, n - 1);

    return F[0][0];
}

void multiply(int F[2][2], int M[2][2]) {
    int x = F[0][0] * M[0][0] + F[0][1] * M[1][0];
    int y = F[0][0] * M[0][1] + F[0][1] * M[1][1];
    int z = F[1][0] * M[0][0] + F[1][1] * M[1][0];
    int w = F[1][0] * M[0][1] + F[1][1] * M[1][1];

    F[0][0] = x;
    F[0][1] = y;
    F[1][0] = z;
    F[1][1] = w;
}

void power(int F[2][2], int n) {
    int i;
    int M[2][2] = { { 1, 1 }, { 1, 0 } };

    // n - 1 times multiply the matrix to {{1,0},{0,1}}
    for (i = 2; i <= n; i++)
        multiply(F, M);
}

/* Driver program to test above function */
int main() {
    int n = 9;
    printf("%d", fib(n));

    return 0;
}

輸出:34
時間複雜度: O(n)
空間複雜度: O(1)

方法 5 (對方法4進行優化 )

    上面的方法4可以優化到)的時間複雜度。我們可以像計算x^n那樣,採用遞迴的方式來計算power(M, n) ,C/C++程式碼如下:

#include <stdio.h>

void multiply(int F[2][2], int M[2][2]);

void power(int F[2][2], int n);

/* function that returns nth Fibonacci number */
int fib(int n) {
    int F[2][2] = { { 1, 1 }, { 1, 0 } };
    if (n == 0)
        return 0;
    power(F, n - 1);
    return F[0][0];
}

/* Optimized version of power() in method 4 */
void power(int F[2][2], int n) {
    if (n == 0 || n == 1)
        return;
    int M[2][2] = { { 1, 1 }, { 1, 0 } };

    power(F, n / 2);
    multiply(F, F);

    if (n % 2 != 0)
        multiply(F, M);
}

void multiply(int F[2][2], int M[2][2]) {
    int x = F[0][0] * M[0][0] + F[0][1] * M[1][0];
    int y = F[0][0] * M[0][1] + F[0][1] * M[1][1];
    int z = F[1][0] * M[0][0] + F[1][1] * M[1][0];
    int w = F[1][0] * M[0][1] + F[1][1] * M[1][1];

    F[0][0] = x;
    F[0][1] = y;
    F[1][0] = z;
    F[1][1] = w;
}

/* Driver program to test above function */
int main() {
    int n = 9;
    printf("%d", fib(n));

    return 0;
}

輸出:34
時間複雜度: O(Logn)
空間複雜度: 如果考慮遞迴呼叫時棧的大小,則為O(n) ;如果不考慮呼叫棧的話,則為O(1)

方法 6 (O(Log n) 的時間複雜度)

    下面是一個很有趣的計算斐波那契數列第n項的遞迴公式,該公式的時間複雜度為O(Log n)。

如果n是偶數, 則k=n/2,
F(n)=[2F(k-1)+F(k)]F(k)

如果n是奇數,則 k=(n+1)/2
F(n)=F(k)F(k)+F(k-1)F(k-1)

原文連結:原文來自個人公眾號:C you again,歡迎關注

    該公式是如何計算的?上面的公式可以從前面的矩陣冪推算出來:


要證明上面的公式成立,只需做下面的工作即可:

如果n是偶數, 令 k = n/2
如果n是奇數, 令 k = (n+1)/2

    下面是上述過程的C++ 實現:

// C++ Program to find n'th fibonacci Number in
// with O(Log n) arithmatic operations
#include <bits/stdc++.h>
using namespace std;

const int MAX = 1000;

// Create an array for memoization
int f[MAX] = { 0 };

// Returns n'th fuibonacci number using table f[]
int fib(int n) {
    // Base cases
    if (n == 0)
        return 0;
    if (n == 1 || n == 2)
        return (f[n] = 1);

    // If fib(n) is already computed
    if (f[n])
        return f[n];

    int k = (n & 1) ? (n + 1) / 2 : n / 2;

    // Applyting above formula [Note value n&1 is 1
    // if n is odd, else 0.
    f[n] = (n & 1) ?
            (fib(k) * fib(k) + fib(k - 1) * fib(k - 1)) :
            (2 * fib(k - 1) + fib(k)) * fib(k);

    return f[n];
}

/* Driver program to test above function */
int main() {
    int n = 9;
    printf("%d ", fib(n));
    return 0;
}

輸出:34
時間複雜度為:O(Log n) ,因為每次遞迴呼叫時都將問題規模降了一半

方法 7 (使用Java提供的BigInteger類)

     Java提供了BigInteger類,可以很輕易地算出當n很大時的斐波那契數。

// Java program to compute n-th Fibonacci number where n may be large.
import java.math.*;

public class Fibonacci {
    // Returns n-th Fibonacci number
    static BigInteger fib(int n) {
        BigInteger a = BigInteger.valueOf(0);
        BigInteger b = BigInteger.valueOf(1);
        BigInteger c = BigInteger.valueOf(1);
        for (int j = 2; j <= n; j++) {
            c = a.add(b);
            a = b;
            b = c;
        }

        return (a);
    }

    public static void main(String[] args) {
        int n = 1000;
        System.out.println("Fibonacci of " + n + "th term" + " " + "is" + " " + fib(n));
    }
}

當n=1000時,輸入結果如下:

原文連結:原文來自個人公眾號:C you again,歡迎關注

公眾號推薦(資源加油站)

瞭解更多資源請關注個人公眾號:C you again,你將收穫以下資源

1、PPT模板免費下載,簡歷模板免費下載
2、基於web的機票預訂系統基於web的圖書管理系統
3、貪吃蛇小遊戲原始碼
4、各類IT技術分享

文章推薦

推薦一:計算機網路中這些高頻考題,你還在死記硬背嗎?(一),講述內容:IP地址及其分類,子網掩碼的概念,網路號、主機號、直接廣播地址計算方法等。

推薦二:計算機網路中這些高頻考題,你還在死記硬背嗎?(二),講述內容:區域網介面配置、路由器的靜態路由配置、OSPF動態路由協議配置和DHCP伺服器配置。

    以上就是本期的所有內容了,是否對你有幫助呢?瞭解更多演算法請關注公眾號“C you again”。