求職干貨:再也不怕面試官問斐波那契數(shù)列了!
原標(biāo)題:求職干貨:再也不怕面試官問斐波那契數(shù)列了!
??
作者 | 守望
責(zé)編 | 胡巍巍
前言
假如面試官讓你編寫求斐波那契數(shù)列的代碼時,是不是心中暗喜?不就是遞歸么,早就會了。如果真這么想,那就危險了。
遞歸解法
遞歸,在數(shù)學(xué)與計算機科學(xué)中,是指在函數(shù)的定義中使用函數(shù)自身的方法。
斐波那契數(shù)列的計算表達式很簡單:
F(n) = n; n = 0,1
F(n) = F(n-1) + F(n-2),n >= 2;
因此,我們能很快根據(jù)表達式寫出遞歸版的代碼:
/*fibo.c*/
#include<stdio.h>
#include<stdlib.h>
/*求斐波那契數(shù)列遞歸版*/
unsignedlongfibo(unsignedlongintn)
{
if(n <= 1)
returnn;
else
returnfibo(n-1) + fibo(n-2);
}
intmain(intargc,char*argv[])
{
if(1>= argc)
{
printf("usage:./fibo numn");
return-1;
}
unsignedlongn = atoi(argv[1]);
unsignedlongfiboNum = fibo(n);
printf("the %lu result is %lun",n,fiboNum);
return0;
}
關(guān)鍵代碼只有4行。簡潔明了,一氣呵成。
編譯:
gcc-ofibofibo.c
運行計算第5個斐波那契數(shù):
$ time ./fibo 5
the 5result is 5
real0m0.001s
user 0m0.001s
sys 0m0.000s
看起來并沒有什么不妥,運行時間也很短。
繼續(xù)計算第50個斐波那契數(shù)列:
$ time ./fibo 50
the 50result is 12586269025
real1m41.655s
user 1m41.524s
sys 0m0.076s
計算第50個斐波那契數(shù)的時候,竟然將近兩多鐘!
遞歸分析
為什么計算第50個的時候竟然需要1分多鐘。
我們仔細分析我們的遞歸算法,就會發(fā)現(xiàn)問題,當(dāng)我們計算fibo(5)的時候,是下面這樣的:
|--F(1)
|--F(2)|
|--F(3)| |--F(0)
| |
|--F(4)||--F(1)
||
|| |--F(1)
| |--F(2)|
||--F(0)
F(5)|
| |--F(1)
| |--F(2)|
|| |--F(0)
|--F(3)|
|
|--F(1)
為了計算fibo(5),需要計算fibo(3),fibo(4);而為了計算fibo(4),需要計算fibo(2),fibo(3)……
最終為了得到fibo(5)的結(jié)果,fibo(0)被計算了3次,fibo(1)被計算了5次,fibo(2)被計算了2次?梢钥吹剑挠嬎愦螖(shù)幾乎是指數(shù)級的!
因此,雖然遞歸算法簡潔,但是在這個問題中,它的時間復(fù)雜度卻是難以接受的。
除此之外,遞歸函數(shù)調(diào)用的越來越深,它們在不斷入棧卻遲遲不出棧,空間需求越來越大,雖然訪問速度高,但大小是有限的,最終可能導(dǎo)致棧溢出。
在linux中,我們可以通過下面的命令查看?臻g的軟限制:
$ulimit-s
8192
可以看到,默認(rèn)?臻g大小只有8M。
一般來說,8M的?臻g對于一般程序完全足夠。如果8M的?臻g不夠使用,那么就需要重新審視你的代碼設(shè)計了。
遞歸改進版
既然我們知道最初版本的遞歸存在大量的重復(fù)計算,那么我們完全可以考慮將已經(jīng)計算的值保存起來,從而避免重復(fù)計算,該版本代碼實現(xiàn)如下:
/*fibo0.c*/
#include<stdio.h>
#include<stdlib.h>
/*求斐波那契數(shù)列,避免重復(fù)計算版本*/
unsignedlongfiboProcess(unsignedlong*array,unsignedlongn)
{
if(n < 2)
returnn;
else
{
/*遞歸保存值*/
array[n] = fiboProcess(array,n-1) + array[n-2];
returnarray[n];
}
}
unsignedlongfibo(unsignedlongn)
{
if(n <= 1)
returnn;
unsignedlongret = 0;
/*申請數(shù)組用于保存已經(jīng)計算過的內(nèi)容*/
unsignedlong*array= (unsignedlong*)calloc(n+1,sizeof(unsignedlong));
if(NULL== array)
{
return-1;
}
array[1] = 1;
ret = fiboProcess(array,n);
free(array);
array= NULL;
returnret;
}
/**main函數(shù)部分與fibo.c相同,這里省略*/
效率如何呢?
$ gcc -o fibo0 fibo0.c
$ time ./fibo0 50
the 50result is 12586269025
real0m0.002s
user 0m0.000s
sys 0m0.002s
可見其效率還是不錯的,時間復(fù)雜度為O(n)。
但是特別注意的是,這種改進版的遞歸,雖然避免了重復(fù)計算,但是調(diào)用鏈仍然比較長。
迭代解法
既然遞歸法不夠優(yōu)雅,我們換一種方法。如果不用計算機計算,讓你去算第n個斐波那契數(shù),你會怎么做呢?
我想最簡單直接的方法應(yīng)該是:知道第一個和第二個后,計算第三個;知道第二個和第三個后,計算第四個,以此類推。
最終可以得到我們需要的結(jié)果。這種思路,沒有冗余的計算。基于這個思路,我們的C語言實現(xiàn)如下:
/*fibo1.c*/
#include<stdio.h>
#include<stdlib.h>
/*求斐波那契數(shù)列迭代版*/
unsignedlongfibo(unsignedlongn)
{
unsignedlongpreVal = 1;
unsignedlongprePreVal = 0;
if(n <= 2)
returnn;
unsignedlongloop = 1;
unsignedlongreturnVal = 0;
while(loop < n)
{
returnVal = preVal +prePreVal;
/*更新記錄結(jié)果*/
prePreVal = preVal;
preVal = returnVal;
loop++;
}
returnreturnVal;
}
/**main函數(shù)部分與fibo.c相同,這里省略*/
編譯并計算第50個斐波那契數(shù):
$ gcc -o fibo1 fibo1.c
$ time ./fibo1 50
the 50result is 12586269025
real0m0.002s
user 0m0.000s
sys 0m0.002s
可以看到,計算第50個斐波那契數(shù)只需要0.002s!時間復(fù)雜度為O(n)。
尾遞歸解法
同樣的思路,但是采用尾遞歸的方法來計算。
要計算第n個斐波那契數(shù),我們可以先計算第一個,第二個,如果未達到n,則繼續(xù)遞歸計算,尾遞歸C語言實現(xiàn)如下:
/*fibo2.c*/
#include<stdio.h>
#include<stdlib.h>
/*求斐波那契數(shù)列尾遞歸版*/
unsignedlongfiboProcess(unsignedlongn,unsignedlongprePreVal,unsignedlongpreVal,unsignedlongbegin)
{
/*如果已經(jīng)計算到我們需要計算的,則返回*/
if(n == begin)
returnpreVal+prePreVal;
else
{
begin++;
returnfiboProcess(n,preVal,prePreVal+preVal,begin);
}
}
unsignedlongfibo(unsignedlongn)
{
if(n <= 1)
returnn;
else
returnfiboProcess(n,0,1,2);
}
/**main函數(shù)部分與fibo.c相同,這里省略*/
效率如何呢?
$ gcc -o fibo2 fibo2.c
$ time ./fibo2 50
the 50result is 12586269025
real0m0.002s
user 0m0.001s
sys 0m0.002s
可見,其效率并不遜于迭代法。尾遞歸在函數(shù)返回之前的最后一個操作仍然是遞歸調(diào)用。
尾遞歸的好處是,進入下一個函數(shù)之前,已經(jīng)獲得了當(dāng)前函數(shù)的結(jié)果,因此不需要保留當(dāng)前函數(shù)的環(huán)境,內(nèi)存占用自然也是比最開始提到的遞歸要小。時間復(fù)雜度為O(n)。?
?
矩陣快速冪解法
這是一種高效的解法,需要推導(dǎo),對此不感興趣的可直接看最終推導(dǎo)結(jié)果。
下面的式子成立是顯而易見的,不多做解釋。
如果a為矩陣,等式同樣成立,后面我們會用到它。假設(shè)有矩陣2*2矩陣A,滿足下面的等式:
可以得到矩陣A:
因此也就可以得到下面的矩陣等式:
再進行變換如下:
以此類推,得到:
實際上f(n)就是矩A^(n-1)中的A[0][0],或者是矩A^n中的A[0][1]。
那么現(xiàn)在的問題就歸結(jié)為,如何求A^n,其中A為2*2的矩陣。
根據(jù)我們最開始的公式,很容易就有思路,代碼實現(xiàn)如下:
/*fibo3.c*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_COL 2
#define MAX_ROW 2
typedef unsigned long MatrixType;
/*計算2*2矩陣乘法,這里沒有寫成通用形式,有興趣的可以自己實現(xiàn)通用矩陣乘法*/
int matrixDot(MatrixType A[MAX_ROW][MAX_COL],MatrixType B[MAX_ROW][MAX_COL],MatrixType C[MAX_ROW][MAX_COL])
{
/*C為返回結(jié)果,由于A可能和C相同,因此使用臨時矩陣存儲*/
MatrixType tempMa[MAX_ROW][MAX_COL] ;
memset(tempMa,0,sizeof(tempMa));
/*這里簡便處理*/
tempMa[0][0] = A[0][0] * B[0][0] + A[0][1] *B [1][0];
tempMa[0][1] = A[0][0] * B[0][1] + A[0][1] *B [1][1];
tempMa[1][0] = A[1][0] * B[0][0] + A[1][1] *B [1][0];
tempMa[1][1] = A[1][0] * B[0][1] + A[1][1] *B [1][1];
memcpy(C,tempMa,sizeof(tempMa));
return 0;
}
MatrixType fibo(int n)
{
if(n <=1)
returnn;
MatrixTyperesult[][MAX_COL] = {1,0,0,1};
MatrixTypeA[][2] = {1,1,1,0};
while(n>0)
{
/*判斷最后一位是否為1,即可知奇偶*/
if (n&1)
{
matrixDot(result,A,result);
}
n /= 2;
matrixDot(A,A,A);
}
return result[0][1];
}
/**main函數(shù)部分與fibo.c相同,這里省略*/
該算法的關(guān)鍵部分在于對A^n的計算,它利用了我們開始提到的等式,對奇數(shù)和偶數(shù)分別處理。
相關(guān)文章
- 好暖!圓明園里一群游客悄悄撿樹枝,原來是為了這一家子
- 戴岳云鵬面具搶劫怎么回事?女子為什么戴岳云鵬面具搶劫?
- 廣東端州城區(qū)保潔面積涉嫌虛報 巡查組挽回2800多萬元財政損失
- 百元連號鈔票換“茅臺”是大坑 消費者不明真相頻被蒙騙
- 早報:雷軍博鰲語錄刷爆朋友圈 蘋果CEO將上法庭
- “結(jié)婚4年備孕無果穿2月就懷上” 中脈內(nèi)衣為何還在忽悠
- 聯(lián)合國安理會討論戈蘭高地問題 14國反對美國決定
- 美一高中生因在校受欺凌拔刀刺傷同學(xué)被指控
- 英國議會堵死脫歐協(xié)議所有選項,媒體發(fā)問:脫歐要何去何從?
- 荷蘭國花慘遭亞洲游客摧殘,網(wǎng)友:應(yīng)該拉起“警戒線”
- 酒駕最重死刑,臺當(dāng)局虛晃一招?
- 美媒:補貼新政促電動汽車良性發(fā)展
- 1/4歐洲人想讓AI替“人類做主”,被批“糟糕的想法”
- 口碑餓了么加大投入 推動本地生活服務(wù)數(shù)字化升級
- 中國式八大寬容都是哪些 中國式八大寬容分別是什么意思
- 澳前總理:我不信華為竊取信息,還很佩服華為
- 從醫(yī)院端切入,斯錄欣要做最懂醫(yī)生的臨床試驗數(shù)據(jù)采集系統(tǒng)
- 首提盒區(qū)房覆蓋率,盒馬如何實現(xiàn)“全覆蓋”小目標(biāo)?
- 油輪救起移民卻遭劫持 馬耳他海軍登船奪回
- 拼多多聯(lián)合創(chuàng)始人達達:拼多多難
網(wǎng)友評論

推廣鏈接
最新文章快讀
文章隨機推薦
- 冠生園前董事被猴子踢掉石頭砸死事件詳情介紹
- 普瑞丁左旋肉堿真假如何識別
- 東西林俊呈歌詞 東西歌詞是什么意思
- 《紅海行動》登頂單日票房冠軍 林超賢張譯路演
- 我的寶貝四千金20集、21集、22集分集劇情介紹及預(yù)告
- “極挑團”尋找“先知”入“秘境” 屢次遭遇兩難抉擇
- 《夏至未至》第11-12集預(yù)告:傅小司欲向立夏表白 遇見李嫣然對掐
- 喬四玩過的女明星都有誰?喬四爺?shù)娜颗苏掌?nbsp;喬四死亡真相揭秘
- 時世艱難:泰國兒童拳賽
- 雙十一來了 被種草了《一本好書》的“精神面包”?
- 環(huán)保部:進一步規(guī)范建設(shè)項目危險廢物環(huán)境影響評價
- 武媚娘傳奇中四貴妃最大的boss韋貴妃:武媚娘智斗四貴妃四貴妃如何死的
- 波音研發(fā)下代航天飛機:希望10天內(nèi)10次往返太空
- 他來了請閉眼阮明淮被誰殺死的 蘇北被懷疑是兇手
- 《黃飛鴻之怒海雄風(fēng)》今日上線 趙文卓再續(xù)黃飛鴻經(jīng)典形象
一周熱門文章推薦
- 求職干貨:再也不怕面試官問斐波那契數(shù)列了!
- 好暖!圓明園里一群游客悄悄撿樹枝,原來是為了這一家子
- 戴岳云鵬面具搶劫怎么回事?女子為什么戴岳云鵬面具搶劫?
- 廣東端州城區(qū)保潔面積涉嫌虛報 巡查組挽回2800多萬元財政損失
- 百元連號鈔票換“茅臺”是大坑 消費者不明真相頻被蒙騙
- 早報:雷軍博鰲語錄刷爆朋友圈 蘋果CEO將上法庭
- “結(jié)婚4年備孕無果穿2月就懷上” 中脈內(nèi)衣為何還在忽悠
- 聯(lián)合國安理會討論戈蘭高地問題 14國反對美國決定
- 美一高中生因在校受欺凌拔刀刺傷同學(xué)被指控
- 英國議會堵死脫歐協(xié)議所有選項,媒體發(fā)問:脫歐要何去何從?
- 荷蘭國花慘遭亞洲游客摧殘,網(wǎng)友:應(yīng)該拉起“警戒線”
- 酒駕最重死刑,臺當(dāng)局虛晃一招?
- 美媒:補貼新政促電動汽車良性發(fā)展
- 1/4歐洲人想讓AI替“人類做主”,被批“糟糕的想法”
- 口碑餓了么加大投入 推動本地生活服務(wù)數(shù)字化升級