斐波那契數列的遞迴與迴圈的演算法實現
斐波那契數列,但凡學過程式設計的童鞋們應該都懂,背景就不介紹了(就是大兔子生小兔子的故事),無論是面試還是實際的運用,常見的一個思路就是先用最先基本的辦法實現,然後根據實際要求,一步步改進,優化演算法效率。今天就以斐波那契數列這個大家都很熟悉的為例來小小感受一下。
- Version 1
- long Fibonacci(int n)
- {
- if (n == 0)
- return0;
- elseif (n == 1)
- return1;
- elseif (n > 1)
-
return Fibonacci (n -
- else
- return -1;
- }
這是最基本的遞迴思路,大家的第一個斐波那契數列應該都是寫成這樣的,但是不知道大家有沒有測試過它的效能如何,不測不知道,一測嚇一跳,N=1000,500,100,50都是黑視窗半天跳不出資料,看CPU是100%(我的電腦是25%,但是因為我的電腦是4核的,大家懂的),最後N=45得到的結果是145秒。 效能為什麼會這麼低呢?以N=5為例子,該程式的執行過程如下圖所示,可以想象,N到一定的數目(其實還是很小的數目,比如45.。。。)就會有很多次的遞迴調
用
那麼,自然的想法是,有什麼辦法可以減少遞迴的次數呢?再觀察上圖,可以看出,有重複的遞迴呼叫。比如F(3)就計算過兩次,一個解決的方法就是記錄下來已經得到結果的F(n),避免重複多次計算。
- long Fibonacci2(int n)
- {
- if (n == 0)
- return0;
- elseif (n == 1)
- return1;
- elseif (n > 1)
- {
- if(tempResult[n] != 0)
- return tempResult[n];
- else
- {
-
tempResult[n] = Fibonacci2 (n - 1) + Fibonacci2 (n -
- return tempResult[n];
- }
- }
- }
這次優化之後,效率明顯提高,N=1000時,執行時間仍趨近於0秒,但是當N=5000時出現了棧溢位的情況。再來分析,棧溢位,那麼棧當中有什麼呢?呼叫資訊,變數。我們version2的改進,是將version1的呼叫樹砍掉了一半,所以,要真正解決這個問題,還是要放棄遞迴演算法。大家應該瞭解,常見的改變遞迴演算法的方式是將它變成迴圈。實際上,遞迴是從大往小分解問題,迴圈則是反方向演算法。
- long Fibonacci3(int n)
- {
- long * temp = newlong[n + 1];
- temp[0] = 0;
- if (n > 0)
- temp[1] = 1;
- for(int i = 2; i <= n; ++i)
- {
- temp[i] = temp[i - 1] + temp[i - 2];
- }
- long result = temp[n];
- delete[] temp;
- return result;
- }
現在,當N=1000000的時候,時間仍然小於1秒了。 當然,問題還沒有結束,雖然version3看上去已經是一個效率很好的演算法了。前面的解決方式都是自然的從演算法角度來考慮,但是,數學的力量是偉大的。version3的複雜度是O(n),有沒有對數級的演算法,或者更好的,常量時間演算法呢? 迴歸到高中數學,發現f(n)=f(n-1)+f(n-2)是一個數列的通項公式,經過化簡,我們可以得到它的遞推公式,可以一步得出結果,為O(1)的時間複雜度。但是由於最後的遞推公式中含有無理數,所以不能保證結果的精度。
斐波那契數列:兔子問題及其應用
斐波那契(Fibonacci1170-1250),義大利最傑出的數學家。其父為比薩的商人,他認為數學是有用的,因此送斐波那契向阿拉伯教師們學習數學,掌握了印度數碼之一新的記數體系,後來遊歷埃及、敘利亞、希臘、西西里、法國等地,掌握了不同國家和地區商業的算術體系,1200年回答比薩,潛心研究數學,1202年寫成《算盤全集》,此書廣為流傳,為在歐洲傳播印度-阿拉伯數碼起了重要的作用。
1228斐波那契在修訂《算盤全集》修訂本中,增加了一道非常有名的兔子繁殖問題,問題是這樣的:如果一對兔子每月生一對兔子;一對新生兔,從第二個月起就開始生兔子;假定每對兔子都是一雌一雄,試問一對兔子,一年能繁殖成多少對兔子?
先看前幾個月的情況:第一個月有一對剛出生的兔子,即F(1)=1;第二個月,這對兔子長成成年兔,即F(2)=1;第三個月,這對成年兔生出一對小兔,共有兩對兔子,即F(3)=2;第四個月,成年兔又生出一對小兔,原出生的兔子長成成年兔,共有三對兔子,即F(4)=3;第五個月,原成年兔又生出一對小兔,新成年兔也生出一對小兔,共有五對兔子,即F(5)=5;……以此類推,可得每個月的兔子對數,組成數列:1,1,2,3,5,8,13,21,34,55,89,144,…,這就是著名的斐波那契數列,其中的任一個數,都叫斐波那契數。
題中本質上有兩類兔子:一類是能生殖的兔子,稱為成年兔子;新生的兔子不能生殖;新生兔子一個月就長成成年兔子。求的是成年兔子與新生兔子的總和。每月新生兔對數等於上月成年兔對數。每月成年兔對數等於上個月成年兔對數與新生兔對數之和。最後得關係式:
F(1)=F(2)=1;
F(n)=F(n-1)+F(n-2) (n≥3)。
法國數學家比內(Binet)證明了通項公式為
#include<iostream>
using namespace std;
int main()
{
int i,a[30]={0,1,1};//陣列的定義,用來儲存資料
for(i=3;i<30;i++)
{
a[i]=a[i-1]+a[i-2];//Fibonacci的算術方程
}
for(i=1;i<30;i++)
{
printf("a[%d]=%d\n",i,a[i]);//輸出
}
}
C++基礎譚浩強書裡的程式
#include<iostream>
#include<iomanip>
using namespace std;
int main()
{
long f1,f2;
int i;
f1=f2=1;
for(i=1;i<20;i++)
{
cout<<setw(12)<<f1<<setw(12)<<f2;
if(i%2==0)cout<<endl;
f1=f1+f2;
f2=f2+f1;
}
return 0;
}