1. 程式人生 > 實用技巧 >求斐波那契數,你還在用遞迴嗎?

求斐波那契數,你還在用遞迴嗎?

1、什麼是斐波那契數?

  • 斐波那契數,又稱黃金分割數列、因數學家萊昂納多·斐波那契(Leonardoda Fibonacci)以兔子繁殖為例子而引入,故又稱為“兔子數列”,指的是這樣一個數列:0、1、1、2、3、5、8、13、21、34、……
  • 斐波那契數,通常用F(n) 表示,形成的序列稱為斐波那契數列。該數列由0 和 1 開始,後面的每一項數字都是前面兩項數字的和。也就是:
    F(0) = 0, 
    F(1)= 1
    F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
    給定N,計算F(N)。

2、經典解法-遞迴

根據題意,我們可以用遞迴求解。

    public int fib(int N) {
        if (N < 2) {
            return N;
        }
        return fib(N-1) + fib(N-2);
    }

但是這個是最好的解法嗎?

  • 假如我們要計算的N為10,我們要先計算fib(9),再計算fib(8);
  • 計算fib(9), 要計算fib(8)和fib(7),如此遞迴下去
  • 計算fib(8), 要計算fib(7)和fib(6),如此遞迴下去

發現什麼問題沒?

  • 越往小了算,重複計算越多, 時間效率n的n次方,不可想象
  • fib(30)耗時 4毫秒
  • fib(50)耗時 62秒
  • fib(100)耗時(1h沒結果,停了)

那有沒有其它的好辦法呢?

  • 你發現沒有我們用遞迴是從大到小計算,從fib(N)到fib(N-1)...fib(0)
  • 但是我們fib(N)不知道結果,我們只知道fib(0)和fib(1)
  • 我們可不可以試著從小到大計算

3、動態規劃

我們嘗試從小往大推演,把計算結果儲存下來,避免重複計算,這種方法一般稱之為動態規劃。

    public int fib(int N) {
        if (N < 2) {
            return N;
        }
        int[] cache = new int[N+1];
        cache[0] = 0;
        cache[1] = 1;
        for (int i = 2; i <= N; i++) {
            cache[i] = cache[i-1] + cache[i-2];
        }
        return cache[N];
    }
  • 上面的程式碼: cache[i] = cache[i-1] + cache[i-2]稱之為動態規劃公式
  • 時間效率n, 遍歷一次即可
  • 空間效率n,快取n個數據
  • 空間效率還可以改進

4、動態規劃改進

  • 我們用m代表fib(n-2), n代表fib(n-1), o代表fib(n)
  • 每次計算結束再重置 m 和 n
    public int fib(int N) {
        if (N < 2) {
            return N;
        }
        int m = 0, n = 1;
        for (int i = 2; i <= N; i++) {
            int o = n + m;
            m = n;
            n = o;
        }
        return n;
    }
  • 時間效率n, 遍歷一次即可
  • 空間效率3
  • 還有一種數學解法,有興趣的可以自己去了解