前言假如面試官讓你編寫求斐波那契數列的**時,是不是心中暗喜?不就是遞迴麼,早就會了。如果真這麼想,那就危險了。
遞迴求斐波那契數列
遞迴,在數學與電腦科學中,是指在函式的定義中使用函式自身的方法。
斐波那契數列的計算表示式很簡單:
1f(n) = n; n = 0,1
2f(n) = f(n-1) + f(n-2),n >= 2;
因此,我們能很快根據表示式寫出遞迴版的**:
1/fibo.c/
2#include
3#include
4/求斐波那契數列遞迴版/
5unsigned long fibo(unsigned long int n)
612int main(int argc,char *ar**)
1319 unsigned long n = atoi(ar**[1]);
20 unsigned long fibonum = fibo(n);
21 printf(「the %lu result is %lu\n」,n,fibonum);
22 return 0;
23}關鍵**為3~9行。簡潔明瞭,一氣呵成。
編譯:1gcc -o fibo fibo.c
執行計算第5個斐波那契數:
1$ time ./fibo 5
2the 5 result is 5
34real 0m0.001s
5user 0m0.001s
6sys 0m0.000s
看起來並沒有什麼不妥,執行時間也很短。
繼續計算第50個斐波那契數列:
1$ time ./fibo 50
2the 50 result is 12586269025
34real 1m41.655s
5user 1m41.524s
6sys 0m0.076s
計算第50個斐波那契數的時候,竟然花了將近兩分鐘!而且如果你在fibo函式中定義一些區域性變數,時間將會變得更長!
遞迴分析
為什麼計算第50個的時候竟然需要1分多鐘。我們仔細分析我們的遞迴演算法,就會發現問題,當我們計算fibo(5)的時候,是下面這樣的:
|--f(1)
|--f(2)|
|--f(3)| |--f(0)
| |
|--f(4)| |--f(1)
| |
| | |--f(1)
| |--f(2)|
| |--f(0)
因此,雖然遞迴演算法簡潔,但是在這個問題中,它的時間複雜度卻是難以接受的。除此之外,遞迴函式呼叫的越來越深,它們在不斷入棧卻遲遲不出棧,空間需求越來越大,雖然訪問速度高,但大小是有限的,最終可能導致棧溢位。
在linux中,我們可以通過下面的命令檢視棧空間的軟限制:
1$ ulimit -s
28192
可以看到,預設棧空間大小只有8m。一般來說,8m的棧空間對於一般程式完全足夠。如果8m的棧空間不夠使用,那麼就需要重新審視你的**設計了。
迭代解法
既然遞迴法不夠優雅,我們換一種方法。如果不用計算機計算,讓你去算第n個斐波那契數,你會怎麼做呢?我想最簡單直接的方法應該是:知道第乙個和第二個後,計算第三個;知道第二個和第三個後,計算第四個,以此類推。最終可以得到我們需要的結果。這種思路,沒有冗餘的計算。基於這個思路,我們的c語言實現如下:
1/fibo1.c/
2#include
3#include
4/求斐波那契數列迭代版/
5unsigned long fibo(unsigned long n)
621 return returnval;
22}23/*main函式部分與fibo.c相同,這裡省略/
編譯並計算第50個斐波那契數:
1$ gcc -o fibo1 fibo1.c
2$ time ./fibo1 50
3the 50 result is 12586269025
45real 0m0.002s
6user 0m0.001s
7sys 0m0.002s
可以看到,計算第50個斐波那契數隻需要0.002s!時間複雜度為o(n)。
尾遞迴解法
同樣的思路,但是採用尾遞迴的方法來計算。要計算第n個斐波那契數,我們可以先計算第乙個,第二個,如果未達到n,則繼續遞迴計算,尾遞迴c語言實現如下:
1/fibo2.c/
2#include
3#include
4/求斐波那契數列尾遞迴版/
5unsigned long fiboprocess(unsigned long n,unsigned long prepreval,unsigned long preval,unsigned long begin)
615}
1617unsigned long fibo(unsigned long n)
1824
25/*main函式部分與fibo.c相同,這裡省略/
效率如何呢?
1$ gcc -o fibo2 fibo2.c
2$ time ./fibo2 50
3the 50 result is 12586269025
45real 0m0.002s
6user 0m0.001s
7sys 0m0.002s
可見,其效率並不遜於迭代法。尾遞迴在函式返回之前的最後乙個操作仍然是遞迴呼叫。尾遞迴的好處是,進入下乙個函式之前,已經獲得了當前函式的結果,因此不需要保留當前函式的環境,記憶體占用自然也是比最開始提到的遞迴要小。時間複雜度為o(n)。
總結總結一下遞迴的優缺點:
優點:實現簡單
可讀性好
缺點:遞迴呼叫,占用空間大
遞迴太深,易發生棧溢位
可能存在重複計算
面試官期待的實用斐波那契解法
單說斐波那契數列 我們熟悉的斐波那契數列的問題即 0 n 0f n 1 n 1f n 1 f n 2 n 1 挑剔的面試官不會喜歡的解法,效率很低 long long fibonacci1 const unsigned n 我們乍一看我們剛學習斐波那契數列的時候就是這種解題思路,我們幾乎非常快就能寫...
斐波那契數列的
includeint main return 0 似乎是個斐波那契數列的變形。f0 0 f1 1 fn fn 1 fn 2 當然,2012級同學的第一次練習的第乙個題不應該如此複雜,於是我們簡化一下,我們約定fn表示斐波那契數列的第n項 n 50 你能知道斐波那契數中的任何一項嗎?輸入處理到檔案結束...
迴圈斐波那契數列 斐波那契數列的兩種實現
最先研究這個數列的人是義大利人斐波那契,leonardo fibonacci,他在描述兔子生長的數目時用上了這數列 每個月兔子的總對數,就是這樣乙個序列 1,1,2,3,5,8,13,21.這個序列從第三項開始,每一項都等於前兩項之和。在數學上,斐波那契數列是以遞迴的方法來定義 f 1 1 f 2 ...