算法#02--斐波那契Fibonacci數列算法優化

算法列表

本文從時間效率和佔用空間內存角度評估,找出最優算法。

  1. 經典遞歸算法Recursive algorithm(很慢)
  2. 動態存儲算法Dynamic programming(慢)
  3. 矩陣冪算法Matrix exponentiation(快)
  4. 倍數公式算法Fast doubling(很快)
  5. 倍數公式算法+快速乘法Fast doubling with Karatsuba(最快)

Fibonacci數列

1.數列介紹

斐波那契數列(Fibonacci sequence),又稱黃金分割數列、因數學家列昂納多·斐波那契(Leonardoda Fibonacci)以兔子繁殖爲例子而引入,故又稱爲“兔子數列”,指的是這樣一個數列:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, …

2.數列規律

當我們將這些數字畫成正方形時,會得到一個螺旋擴大的圖形,如下。
螺旋

數列的規律是:F(n)= F(n-1) + F(n-2)

而且當n趨向於無窮大時,前一項與後一項的比值越來越逼近黃金分割0.618(或者說後一項與前一項的比值小數部分越來越逼近0.618)。

1÷1=1,1÷2=0.5,2÷3=0.666…,3÷5=0.6,5÷8=0.625…,
55÷89=0.617977…144÷233=0.618025…46368÷75025=0.6180339886……

越到後面,這些比值越接近黃金比。

3.通用公式:

公式

或者(φ爲 1.618034…)
公式

經典遞歸算法

1.遞歸公式

F(n)= F(n-1) + F(n-2)

2.代碼

private static long i = 0;

public static long F(int n)  {      
    if (n == 0)
        return 0;
    if (n == 1) 
        return 1;

    i++;//記錄計算次數
    return F(n-1) + F(n-2);
}
public static void main(String[] args)  {
    for (int n = 0; n < 40; n++)
        System.out.println(n + " " + F(n)+ " " + i + " "+ (long)Math.pow(1.618, n+1));
}    

輸出:
0 0 0 1
1 1 0 2
2 1 1 4
3 2 3 6
4 3 7 11
5 5 14 17
…….
35 9227465 39088132 33360044
36 14930352 63245948 53976552
37 24157817 102334116 87334061
38 39088169 165580101 141306511
39 63245986 267914255 228633935

3.評估

時間效率–>運算次數O(φ^n) 。n越大,越接近此值,見上最後兩列。
空間內存–>佔用內存O(n)

動態存儲算法

將計算過的F(n-1)和F(n-2)儲存起來,不用再次計算。

1.代碼

public static long F(int n)
{
    long 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;
}

public static void main(String[] args)
{
    for (int n = 0; n < 100; n++)
        System.out.println(n + " " + F(n));
}

2.評估
時間效率–>運算次數O(n)
空間內存–>佔用內存O(1)

矩陣冪算法

1.公式

公式

2.證明

證明過程

3.代碼

public static long fib(int n)
{
    long[][] f = {{1,1},{1,0}};
    if (n == 0)
        return 0;
    if( n == 1)
        return 1;

    return power(f, n-1);
}

public static long power(long[][] f, int n)
{
    if( n == 0)
        return 0;
    if( n == 1)
        return 1;

    long[][] m = {{1,1},{1,0}};

    power(f, n/2);
    long x = multiply(f, f); 

    if (n % 2 != 0)
        x = multiply(f, m);

  return x;
}

public static long multiply(long[][] f, long[][] m)
{
  long x =  f[0][0]*m[0][0] + f[0][1]*m[1][0];
  long y =  f[0][0]*m[0][1] + f[0][1]*m[1][1];
  long z =  f[1][0]*m[0][0] + f[1][1]*m[1][0];
  long 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;

  return x;
}

public static void main(String[] args)
{
    for (int n = 0; n < 100; n++)
        System.out.println(n + " " + fib(n));
}

4.評估
時間效率–>運算次數O(logn)
空間內存–>佔用內存O(1)

倍數公式算法

1.公式

公式

2.證明

在矩陣冪公式基礎上。

證明過程

3.代碼

放到下一個算法裏。

倍數公式算法+快速乘法

1.公式

還是跟倍數公式算法一樣,只是裏面的乘法用快速乘法替代。

首先介紹快速乘法。

Karatsuba乘法是一種快速乘法算法。由Anatolii Alexeevitch Karatsuba於1960年提出,並於1962年發表。一般我們做高精度乘法,普通算法的複雜度爲O(n^2),而Karatsuba算法的複雜度爲O(3n^log3) ≈ O(3n^1.585)。

2.證明

Karatsuba算法主要應用於兩個大數的相乘,原理是將大數分成兩段後變成較小的數位,然後做3次乘法。推導過程如下:

計算兩個很大的數xy相乘,取一個正整數m。
推導過程

3.代碼

//倍數公式算法
private static long Fibonacci(int n) {
    long a = 0;
    long b = 1;     

    for (int i = 31; i >= 0; i--) {//n最大爲2^31-1,所以只需要移位32次
        long d = a * (b * 2 - a);
        long e = a * a + b * b;
        a = d;
        b = e;
        if (((n >> i) & 1) != 0) {
            long c = a + b;
            a = b;
            b = c;
        }
    }
    return a;
}

//倍數公式算法+快速算法
private static long FibonacciWithKaratsuba(int n) {
    long a = 0;
    long b = 1;     

    for (int i = 31; i >= 0; i--) {
        long d = Karatsuba(a, b * 2 - a);
        long e = Karatsuba(a, a) + Karatsuba(b, b);
        a = d;
        b = e;
        if (((n >> i) & 1) != 0) {
            long c = a + b;
            a = b;
            b = c;
        }
    }
    return a;
}

//快速算法
public static long Karatsuba(long x, long y){
    if((x < 10) || (y < 10)){
        return x * y;
    }

    String s1 = String.valueOf(x);
    String s2 = String.valueOf(y);      
    int maxLength = Math.max(s1.length(), s2.length());
    int m = (int)Math.pow(10, maxLength/2);//取10 的(maxLength長度一半)次冪爲除數      

    long xHigh = x / m;
    long xLow = x % m;
    long yHigh = y / m;
    long yLow = y % m;

    long a = Karatsuba(xHigh, yHigh);       
    long b = Karatsuba((xLow + xHigh), (yLow + yHigh));
    long c = Karatsuba(xLow, yLow); 

    return a * m * m + (b - a - c) * m + c;
}   

public static void main(String[] args)  {
    for (int N = 0; N < 100; N++)
        System.out.println(N + " " + Fibonacci(N) + " "+ FibonacciWithKaratsuba(N));
}   

輸出結果一樣。

0 0 0
1 1 1
2 1 1
3 2 2
4 3 3
5 5 5
….
45 1134903170 1134903170
46 1836311903 1836311903
47 2971215073 2971215073
48 4807526976 4807526976
49 7778742049 7778742049
50 12586269025 12586269025

4.評估
時間效率–>運算次數O(logn)
空間內存–>佔用內存O(1)

算法時間效率對比

運行環境:
Intel Core 2 Quad Q6600 (2.40 GHz)
單線程
Windows XP SP 3, Java 1.6.0_22.

這裏寫圖片描述

這裏寫圖片描述

這裏寫圖片描述

單位ns。
這裏寫圖片描述

參考資料:
https://www.nayuki.io/page/fast-fibonacci-algorithms

https://en.wikipedia.org/wiki/Karatsuba_algorithm

https://www.mathsisfun.com/numbers/fibonacci-sequence.html

發佈了45 篇原創文章 · 獲贊 21 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章