什么是斐波那契數列?
斐波那契數列是這樣一個數列,它滿足:
f(0) = 0;
f(1) = 1;
f(n) = f(n-1) + f(n-2) (當n>=2時)
到底有幾種方法,這些思路里蘊含的優化思路究竟是怎么樣的,今天和大家聊一聊。
一、遞歸法
偽代碼:
uint32_t f(uint32_t n){
if(n0) return 0;
if(n1) return 1;
return f(n-1)+f(n-2);
}
思路:這是一個遞歸的代碼,非常清晰,直接把斐波那契數列的定義翻譯成了代碼。
例如:
假設要求f(5)
f(5) = f(4) + f(3);
於是會遞歸計算f(4)和f(3);
接着要求f(4)
f(4) = f(3)+ f(2);
於是會遞歸計算f(3)和f(2);
可以看到,計算f(5)和f(4)中都要計算f(3),但這兩次f(3)會重復計算,這就是遞歸的最大問題,對於同一個f(a),不能復用。
計算一個f(n)到底需要有多少次遞歸調用呢?
我們可以在代碼里加一個計數驗證一下。
偽代碼:
static uint32_t count=0; // 加一個全局變量計數
uint32_t f(uint32_t n){
count++; // 遞歸一次,計數加一
if(n0) return 0;
if(n1) return 1;
return f(n-1)+f(n-2);
}
實驗的結果:
f(5) count = 15
f(10) count = 177
f(15) count = 1K+
f(20) count = 2W+
f(25) count = 24W+
f(30) count = 269W+
f(35) count = 2986W+
f(40) count = 3.3Y+
畫外音:
(1)這個count,是函數遞歸了對少次;
(2)f(45),機器居然算不出來;
(3)對結論有疑問的,自己可以run一把;
啟示:
(1)斐波那契數列求解,如果用直接法,時間復雜度是指數級的,不可行;
(2)如果沒有太大的把握,工程中盡量少使用遞歸,容易把自己玩死;
二、正推法
從斐波那契數列的定義:
f(0) = 0;
f(1) = 1;
f(n) = f(n-1) + f(n-2) n>=2時
可以看出,每一個新的f(n),是前兩個舊的f(n-1)和f(n-2)之和,一路遞歸下去,最終都將遞歸到f(0)和f(1)上來。
反過來想,我們不倒着f(n),f(n-1),f(n-2)這么計算,而是f(0),f(1),f(2)…f(n)這么正向計算,豈不是更快么?
偽代碼:
uint32_t f(uint32_t n){
uint32_t arr[n];
arr[0]=0;
arr[1]=1;
for(uint32_t i=2;i<=n;i++){
arr[i]=arr[i-1]+arr[i-2];
}
return arr[n];
}
這么正向的計算,只需要一個for循環,就能夠計算出f(n)的值,時間復雜度是O(n)。
三、通項公式法
f(0) = 0;
f(1) = 1;
f(n) = f(n-1) + f(n-2) (當n>=2時)
大學學過相關課程,可解出f(n)通項公式。
畫外音:額,是不是有朋友讀了個假大學。
線性遞推數列:
f(n) = f(n-1) + f(n-2)
對應的特征方程是:
x^2 = x + 1
求解特征方程得到:
x1=(1+√5)/2
x2=(1-√5)/2
於是得到:
f(n) = a1(x1)^n + a2(x2)^n
將:
f(0) = 0;
f(1) = 1;
代入上述通項公式。
於是得到:
a1=1/√5
a2=-1/√5
於是最終得到:
f(n)=(1/√5)*{[(1+√5)/2]^n -[(1-√5)/2]^n}
畫外音:別問我為什么懂這些,我TM作為計算機信息安全專業,也被數論,有限域,加密解密這些數學學科折磨過。
可忽略上述吹牛*的過程,百度一下能得到答案。
總之,得到了斐波那契數列通項公式:
f(n) = a1(b1)^n + a2(b2)^n
其中a1, b1, a2, b2四個數字都是常數。
想求f(45),把n=45帶入上述通項公式即可。
那么,帶入通項公式求解,時間復雜度是多少呢?是O(1)么?
通項公式的計算,並不能O(1)得到,而是一個a^n,即power(a, n)的求解過程。
那么,如何求解a的n次方呢?
最粗暴的方法,將a不斷的自乘n次。
偽代碼:
uint64_t power(uint64_t a, uint64_t n){
uint64_t result=a;
for(uint64_t i=1;i<n;i++){
result *=a;
}
return result;
}
很容易知道,a通過for循環不斷自乘,求解a^n的時間復雜度是O(n)。
通過“正推”法,求解f(n)的時間復雜度是O(n)。
轉載拜托,面試別再問我斐波那契數列了!!!