斐波那契数列的定义
1.n==1 || n==2 A(n) = 1
2.An = A(n-1)+A(n-2)
递归法:
int fibonacci(int n){
assert(n > 0);
//递归出口
if(n == 1 || n == 2){
return 1;
}
return fiboancci(n-1)+fibonacci(n-2);
}
这样的递归算法时间复杂度达到了O(2^n),当数据规模到达一定程度时,是不可接受的算法。为什么时间复杂度会如此之高,对于给定的一个项数n(n>=2),每次求解都需要两次进行递归,所以时间复杂度为O(2^n)。而其中还包括很多重复计算的子问题,如求解fib(4)已经知道了fib(2),但是在计算fib(3)又一次求解了fib(2),若给定的项数n较大时,其中包括非常之多的重复子问题。如何进行优化呢?
动态规划(记忆化搜索)
动态规划正是用来解决问题当中会出现重复子问题的方法。动态规划通过记录子问题的解,来避免当下次出现相同子问题时进行重复的计算。而通过递归写法的动态规划也称为记忆化搜索,通过递归记录子问题的解,一般将解存储在数组,而后通过索引找到对应问题的解。
#include <cstdio>
#include <cstring>
#include <cassert>
const int maxn = 1000;
int dp[maxn];//全局的变量存储.data段,如果在函数中开辟会占用大量的栈空间
long long fibonacci(int n){
assert(n > 0);//防止传入错误的数据,进行断言
if(n == 1||n == 2){
return 1;
}
//搜索是否有对应的解,没有则存储
if(dp[n] == -1){
dp[n] = fibonacci(n-1)+fibonacci(n-2);
}
return dp[n];//搜索成功
}
int main(){
memset(dp,-1,sizeof(dp));//初始化记录解的dp数组
int n;
//注意fibonacci的项,太大会溢出
scanf("%d",&n);
printf("fib(%d) = %lld\n",n,fibonacci(n));
return 0;
}
通过记忆化搜索的方式,只需要O(n)的时间复杂度即可计算出fibonacci数列的第n的值,相比直接递归求解时间复杂度O(2^n)得到了大大的提升,算法的性能显著提高。
动态规划(循环+递推)
#include <cstdio>
#include <cstring>
#include <cassert>
const int maxn = 1000;
int dp[maxn];
int main(){
memset(dp,-1,sizeof(dp));
int n;//注意输入的项数
scanf("%d",&n);
dp[0] = 1;
dp[1] = 1;
for(int i=2;i < n;++i){
dp[i] = dp[i-1]+dp[i-2];//递推
}
printf("%d",dp[n-1]);
return 0;
}
时间复杂度为O(n),在循环+递推写法的动态规划,相比递归的写法更好理解,但是其本质还是相同的。都是为了解决当问题中出现重复子问题而进行重复计算的问题。
值得注意的是,使用动态规划这种思想解决问题的前提是,一个问题当中必须有重复的子问题才能使用动态规划进行解决。