算法与数据结构---6.6、斐波那契数列-记忆化递归
一、总结
一句话总结:
记忆化递归,就是把已经计算的中间状态保存下来,下次需要的时候就直接拿这个结果,就避免了递归中的重复计算中间状态
#include <iostream>
#include <cstring>
using namespace std;
const int mod=1000000007;
int cache[200000];
int find(int n){
//就是如果缓存中有,就直接拿缓存
//否则计算,然后将计算的结果保存到缓存
if(cache[n]!=-1) return cache[n];
else{
return cache[n]=(find(n-1)+find(n-2))%mod;
}
}
int main(){
int n;
cin>>n;
memset(cache,-1,sizeof(cache));
cache[2]=cache[1]=1;
cout<<find(n)<<endl;
return 0;
}
二、斐波那契数列
博客对应课程的视频位置:6.6、斐波那契数列-记忆化递归
https://www.fanrenyi.com/video/27/278
1、题目描述
问题描述:
满足F1=F2=1,F(n)=F(n-1)+F(n-2)的数列称为斐波那契数列(Fibonacci),
它的前若干项是1,1,2,3,5,8,13,21,34,55...,求此数列第n项 mod 10^9+7的值(n>=3)。
输入格式:
一行一个正整数n
输出格式:
一行一个整数表示答案。
输入输出样例:
输入5,输出5
输入10,输出55
【数据范围】
对于60%的数据,1<=n<=92;
对于100%的数据,1<=n<2^63。
题目位置:
P1962 斐波那契数列 - 洛谷 | 计算机科学教育新生态
https://www.luogu.com.cn/problem/P1962
2、递推解法
1 /*
2
3 递推关系式: 4 题目中已经非常明显的给出了,就是 5 F(n)=F(n-1)+F(n-2) 6 7 解决递推问题的一般步骤 8 1、建立递推关系式:F(n)=F(n-1)+F(n-2) 9 2、确定边界条件: 10 f(1)=f(2)=1, 11 所以我们的循环可以从3开始,到n结束, 12 也就是3-n 13 14 算法步骤: 15 1、确定初始值 16 2、循环做递推,3-n 17 18 */ 19 #include <iostream> 20 using namespace std; 21 const int mod=1000000007; 22 int f[200000]; 23 int main(){ 24 int n; 25 cin>>n; 26 //1、确定初始值 27 f[1]=f[2]=1; 28 //2、循环做递推,3-n 29 for(int i=3;i<=n;i++){ 30 //F(n)=F(n-1)+F(n-2) 31 f[i]=(f[i-1]+f[i-2])%mod; 32 } 33 cout<<f[n]<<endl; 34 return 0; 35 }
3、滚动数组优化
1 /*
2
3 之前的最大子段和的动态规划的优化的时候, 4 我们讲了滚动数组优化, 5 原因是 对应的状态转移方程为: 6 f[i]=max(f[i-1]+a[i],a[i]) (2<=i<=n) 7 里面只用到了f[i]和f[i-1]这两个元素, 8 所以可以用只有两个元素的数组来优化 9 10 我们现在的递推表达式是: 11 f[i]=f[i-1]+f[i-2] (3<=i<=n) 12 里面用到了f[i]、f[i-1]和f[i-2]三个元素, 13 所以可以用含有三个元素的数组来优化 14 15 滚动数组的代码修改也很简单 16 直接在递推表达式有i的位置%3即可 17 f[i%3]=f[(i-1)%3]+f[(i-2)%3]; 18 (%3是因为现在是有三个元素的滚动数组) 19 20 注意: 21 取结果的时候,n也需要模3,例如f[n%3] 22 23 */ 24 25 #include <iostream> 26 using namespace std; 27 const int mod=1000000007; 28 int f[3]; 29 int main(){ 30 int n; 31 cin>>n; 32 //1、确定初始值 33 f[1]=f[2]=1; 34 //2、循环做递推,3-n 35 for(int i=3;i<=n;i++){ 36 //F(n)=F(n-1)+F(n-2) 37 f[i%3]=(f[(i-1)%3]+f[(i-2)%3])%mod; 38 } 39 //注意n也需要模3 40 cout<<f[n%3]<<endl; 41 return 0; 42 }
4、递推和动态规划的关系
/*
上述代码也就是这个题目动态规划的写法
动态规划里面有状态,状态转移方程
递推里面初始值,递推表达式
其实动态规划里面的状态转移方程,就是递推表达式
动态规划里面的初始状态,就是递推里面的初始值
所以动态规划可以看做是一种特殊的递推,
动态规划可以看做保存中间状态(中间结果)的递推
对于这题:
状态可以设置为:f[i]为 斐波那契数列第n项 mod 10^9+7的值
那么状态转移方程就是递推表达式:F(n)=F(n-1)+F(n-2)
初始状态:f[1]=f[2]=1
*/
5、三个变量解法
1 /*
2 f[3] 可以直接用3个变量a、b、c来代替
3 这个时候就不能通过取模来自动变换位置了 4 5 6 */ 7 #include <iostream> 8 using namespace std; 9 const int mod=1000000007; 10 int main(){ 11 int n; 12 int a,b,c; 13 cin>>n; 14 //1、确定初始值 15 //这里对a也赋值为1,是为了保证n=1和n=2的时候也有正确结果输出 16 c=a=b=1; 17 //2、循环做递推,3-n 18 for(int i=3;i<=n;i++){ 19 //F(n)=F(n-1)+F(n-2) 20 c=(b+a)%mod; 21 //保留f(n)和f(n-1)做下一轮的f(n-1)和f(n-2) 22 a=b; 23 b=c; 24 } 25 cout<<c<<endl; 26 return 0; 27 }
6、递归写法
1 /*
2
3 本题递推法的递推的关系式非常明确,就是f[i]=f[i-1]+f[i-2] 4 递推法的递推关系式,对应到递归,就是递归的各个元素之间的关系 5 明确这个,递归的代码就特别好敲 6 7 递归注意点 8 递归的终止条件:n=2和n=1 9 递归的递推表达式:f[i]=f[i-1]+f[i-2] (3<=i<=n) 10 递归的返回值:所求值(斐波那契数列第n项 mod 10^9+7的值) 11 12 */ 13 #include <iostream> 14 using namespace std; 15 const int mod=1000000007; 16 17 int find(int n){ 18 if(n==1||n==2) return 1; 19 else{ 20 return (find(n-1)+find(n-2))%mod; 21 } 22 } 23 24 int main(){ 25 int n; 26 cin>>n; 27 cout<<find(n)<<endl; 28 return 0; 29 }
7、记忆化递归
1 /*
2
3 比如我们要求f[6]
4 f[6]=f[5]+f[4]
5 f[5]=f[4]+f[3]
6 f[4]=f[3]+f[2]
7 ...
8
9 我们可以看到,在上述过程中,f[4]、f[3]等都出现了很多次,都被重复计算了很多次
10 这就是递归效率为什么不高的原因
11
12 解决这个问题就用记忆化递归,就是把已经计算的中间状态保存下来,
13 下次需要的时候就直接拿这个结果,就不用重复计算了
14
15
16 记忆化递归的思想和动态规划的思想是一样的,
17 都是保存中间计算结果,避免重复计算,拿空间换时间
18
19
20 */
21
22 #include <iostream>
23 #include <cstring>
24 using namespace std;
25 const int mod=1000000007;
26
27 int cache[200000];
28
29 int find(int n){
30 //就是如果缓存中有,就直接拿缓存
31 //否则计算,然后将计算的结果保存到缓存
32 if(cache[n]!=-1) return cache[n];
33 else{
34 return cache[n]=(find(n-1)+find(n-2))%mod;
35 }
36 }
37
38 int main(){
39 int n;
40 cin>>n;
41 memset(cache,-1,sizeof(cache));
42 cache[2]=cache[1]=1;
43 cout<<find(n)<<endl;
44 return 0;
45 }
来源:oschina
链接:https://my.oschina.net/u/4382322/blog/4304557