面試官問你斐波那契數列的時候不要高興得太早

2021-09-11 08:41:52 字數 3027 閱讀 2104

前言假如面試官讓你編寫求斐波那契數列的**時,是不是心中暗喜?不就是遞迴麼,早就會了。如果真這麼想,那就危險了。

遞迴求斐波那契數列

遞迴,在數學與電腦科學中,是指在函式的定義中使用函式自身的方法。

斐波那契數列的計算表示式很簡單:

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 ...