1. 程式人生 > 其它 >尋找斐波那契數列最優解(C++)

尋找斐波那契數列最優解(C++)

目錄

因為在刷《劍指offer》的時候又又又又遇到了這個題,腦子裡響起了“塔塔開,不塔塔開就無法勝利啊!”,於是我準備好好把斐波那契數列弄明白,然後此文就誕生了。

斐波那契數列簡介

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

在數學上,斐波那契數列以如下被以遞推的方法定義:

F(0)=0

F(1)=1

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

演算法部分

一、原版遞迴
long long fibonacci(unsigned int n) {
     long long f[2] = {0, 1};
     if(n<2)return f[n];
     
     return fibonacci(n-1) + fibonacci(n-2);
}

n = 5

n = 40(嘗試了一下n=100,結果渣機在兩分鐘之內沒跑出來)

#include<iostream>
using namespace std;
int main(void) {
    long long out = fibonacci(40);
	cout << out;
	return 0;
}

消耗時間:0.9454s

當n比較小的時候,運算還是比較愉快的~

但是當n比較大的時候事請就變得嚴重了~

\[time(40)\approx 11*time(5) \]

那麼,為什麼呢?

其實斐波那契數列的遞迴過程可以看作一棵完全二叉樹的建立(以n=5為例):

最後f[5] = f[1] + f[0] + f[1] + f[1] + f[0] + f[1] + f[0] + f[1] = 1 + 0 + 1 + 1 + 0 + 1 + 0 + 1 = 5

我們發現,有好多沒必要的重複計算出現。

比方說f(2)已經求出來了,但是我沒有儲存這個結果,於是計算機就得再求一遍,那麼時間就會增加,這好嗎?這不好!

這個例子f(2)求了3遍 f(3)求了2遍,而求f(5)只需要分別求1遍f(4),f(3),f(2)。

這樣遞迴相當於浪費了一半的時間!

於是捏,我們就可以把已經求過了的f給存起來,給計算機減負。

二、尾遞迴(存值版遞迴)
 long long fibonacci(int f0,int f1,unsigned int n) {
     
     if(n > 0) {
        return fibonacci(f0+f1,f0,n-1);
     }
     
     return f0;
}

n = 40

#include<iostream>
using namespace std;
int main(void) {
    long long out = fibonacci(0,1,5);//當然f0=0,f1=1咯,帶進去就好了
	cout << out;
	return 0;
}

消耗的時間:0.2618s

三、雙指標快取(存值版非遞迴)
long long fibonacci(unsigned int n) {
        long long f[100000] = {0, 1};
        if(n<2)return f[n];
        
        for(size_t i=2;i<=n;i++){
            f[i] = f[i-1] + f[i-2];//每一次迴圈記錄下新的f的值,不重複計算
        }
        
        return f[n];
    }

n = 40

#include<iostream>
using namespace std;
int main(void) {
    long long out = fibonacci(40);
	cout << out;
	return 0;
}

消耗時間:0.1321s

存下值後再進行計算是不是快了很多!!!

但是,俗話說得好,活到老,學到老。

所以有沒有更快、更強的方法呢

別急,馬上安排上!

四、二階矩陣

這個演算法的時間複雜度為log(n),但前提是你要有一點點線代的知識,什麼,你說沒有?那我現場教你!

在數學中,矩陣(Matrix)是一個按照長方陣列排列的複數實數集合,最早來自於方程組係數常數所構成的方陣。這一概念由19世紀英國數學家凱利首先提出。

矩陣求值:

好了教完了,以上知識均來自於百度,但是我為什麼要教你呢?因為你如果不懂這些的話大概率不會自學,不自學就不會往下看,不往下看我就不能裝13,裝不了13我就會很難受,所以為了我能裝13,我必須教大家這個。

首先有這樣一個這個公式:{f(n), f(n-1), f(n-1), f(n-2)} ={1, 1, 1,0}n-1

不知道也很簡單,自己證明一下,上面的知識加上你高中數學知識足夠證明出來。

有了這個公式,要求f(n),我們只需要求矩陣{1, 1, 1,0}的(n-1)次方。

但是如果我們從0開始迴圈,n次方仍然需要n次運算,和存值版沒啥大的區別。

但是!他是乘方啊,乘方就可以分成兩半,這樣我們就不需要n的時間複雜度了,而是二分法所帶來的趨於log(n)的時間複雜度!!!!

別懵,我來舉個例子。

比方說你要求:26

普通人:2 * 2 = 4

​ 4 * 2 = 8

​ 8 * 2 = 16

​ 16 * 2 = 32

​ 32 * 2 = 64

二分人:22 = 2 * 2 = 4

​ 23 = 22 * 2 = 8

​ 26 = 23 * 23 = 8 * 8 = 64

普通人算了五次,而二分人只算了三次

瞭解了這些之後我們直接上程式碼!

struct Matrix2By2//2x2矩陣
{
      Matrix2By2(long long m00 = 0, long long m01 = 0, long long m10 = 0, long long m11 = 0):m_00(m00), m_01(m01), m_10(m10), m_11(m11){}//初始化二元矩陣的四個元素

      long long m_00;
      long long m_01;
      long long m_10;
      long long m_11;
};

Matrix2By2 MatrixMultiply(const Matrix2By2& matrix1, const Matrix2By2& matrix2){
      return Matrix2By2(matrix1.m_00 * matrix2.m_00 + matrix1.m_01 * matrix2.m_10,matrix1.m_00 * matrix2.m_01 + matrix1.m_01 * matrix2.m_11,matrix1.m_10 * matrix2.m_00 + matrix1.m_11 * matrix2.m_10,matrix1.m_10 * matrix2.m_01 + matrix1.m_11 * matrix2.m_11);   
}//矩陣乘法

Matrix2By2 MatrixPower(unsigned int n)
{
      Matrix2By2 matrix;
      if(n == 1)
      {
            matrix = Matrix2By2(1, 1, 1, 0);
      }
      else if(n % 2 == 0)
      {
            matrix = MatrixPower(n / 2);
            matrix = MatrixMultiply(matrix, matrix);
      }
      else if(n % 2 == 1)
      {
            matrix = MatrixPower((n - 1) / 2);
            matrix = MatrixMultiply(matrix, matrix);
            matrix = MatrixMultiply(matrix, Matrix2By2(1, 1, 1, 0));
      }

      return matrix;
}//矩陣乘方

int fibonacci(unsigned int n) {
      int result[2] = {0, 1};
      if(n <= 2)return result[n];

      Matrix2By2 PowerNMinus2 = MatrixPower(n-1);
      return PowerNMinus2.m_00;//返回的就是f[n],記不得可以回去看下公式
    }

n=40

int main(void) {
    long long out = fibonacci(40);
	cout << out;
	return 0;
}

消耗時間:0.09125s

太帥了!

試試之前那哥們兒算不出來的n=100

int main(void) {
    unsigned long long out = fibonacci(100);//不加unsigned就溢位啦,可想而知斐波那契數列的威力巨大
	cout << out;
	return 0;
}

消耗時間:0.1228s

emmmm~

算你勉強合格吧!

大家要好好吃飯,每天都要開開心心的!