斐波那契數列定義
F(0)=1,F(1)=1,F(n)=F(n-1)+F(n-2)
求F(n)
解法1:遞歸
#include <iostream>
using namespace std;
int fib(int n)
{
if(n==0)
return 0;
if(n==1)
return 1;
return fib(n-1)+fib(n-2);
}
int main()
{
cout<<fib(40)<<endl;
return 0;
}
時間複雜度:O(2^n) ,是一個指數級的時間複雜度
那麼現在我們來看下如何優化這個算法。
計算F(5)的過程如下:
可以看出,在這棵遞歸樹上,我們進行了多次重複的計算。比如fib(3)計算了2次,fib(2)計算了3次
那麼就會有一個想法,對於重複的計算,我們有沒有可能只計算一次呢。
如何避免重複運算??
在程序中設置一個全局變量數組
解法2:記憶化搜索
#include <iostream>
#include <vector>
using namespace std;
//在fib基礎上加上記憶化搜索
//遞歸中多次重複計算fib(i) 將計算過的值存儲下來,就可以避免重複計算
vector<int> memo; //存儲fib(i)的值
int fib(int n)
{
if(n==0)
return 0;
if(n==1)
return 1;
if(memo[n]==-1) //只有當fib(n)沒有被計算過時,才遞歸計算
memo[n] = fib(n-1)+fib(n-2);
return memo[n];
}
int main()
{
int n=40;
memo = vector<int>(n+1,-1);
cout<<fib(n)<<endl;
return 0;
}
通過這種方式,就能夠避免重複的計算,每個n只計算一次fib值,算法複雜度O(n)
所以記憶化搜索的實質是在遞歸的基礎上加上記憶化,是一種自上而下解決問題的方式
解法3:動態規劃
動態規劃是一種自下向上的解決問題的方式
動態規劃的定義:
將原問題拆解成若干子問題,同時保存子問題的答案,使得每個子問題只求解一次,最終獲得原問題的答案
大多數動態規劃問題,其本質其實就是遞歸問題
先解決小數據的問題,然後層層遞推,解決更大數據量的問題
#include <iostream>
#include <vector>
using namespace std;
//使用動態規劃解fib
int fib(int n)
{
vector<int> memo; //存儲fib(i)的值
memo = vector<int>(n+1,-1);
memo[0]=0;
memo[1]=1;
//求解每一個fib(i)
for(int i=2; i<=n; i++)
memo[i] = memo[i-1] + memo[i-2];
return memo[n];
}
int main()
{
int n=40;
cout<<fib(n)<<endl;
return 0;
}
時間複雜度O(n)
總結:三者的關係