斐波那契数列的优化方案

题目描述

根据之前的斐波那契数列简单版,可以看出,我们使用递归只能在一秒内算出第 \(46\) 级台阶的走法

这是因为递归是一种极其低效的算法,每次都会重复计算很多数据,它既要把问题分解递到更小的问题上去,还要把答案从小的问题归回到最后的问题上

那么,我们可以思考这样一个问题,能不能直接从小问题入手,慢慢推导到最终的问题上面去呢?

引入这次的核心内容:递推

对于这道题而言,我们可以知道

fib[i] = (fib[i - 1] + fib[i - 2]) % 1000;//注意题目要求取模

而且我们的fib[1]fib[2]都为 \(1\)

所有我们循环从 \(3\) 开始,把题目要求的最大 1e6 的数据都提前计算出来,存在数组中
每次询问第 \(a\) 级台阶时,直接通过下标输出已经计算好的答案

const int N = 1e6;
int fib[N+10];//多开10个空间
void pre(void) {
fib[1] = 1, fib[2] = 1;//初始化递推起点
for (int i = 3; i <= N; i++)//从3开始递推
fib[i] = (fib[i - 1] + fib[i - 2]) % 1000;//递推公式
}

上述的代码,我们定义了一个pre函数,目的是提前预处理出所有可能用到的数据

cin >> n;
while (n--)
{
int a;
cin >> a;
cout << fib[a] << endl;//直接从已有的答案中输出结果
}

以上就是一道简单的递推题目,时间复杂度为\(O(n)\),远远比我们递归(无优化)的\(O(2^n)\)优秀得多

当然,我们也可以对我们的递归进行优化操作——记忆化搜索

在计算fib[i] = fib[i - 1] + fib[i - 2]时,递归到下一层(可以看作有左右两条路)
程序会先走左边的路,计算 fib[i - 1] = fib[i - 2] + fib [i - 3],直到这条路走到底
然后归回这条路的答案到上一次,再走上一层的右边的路

不难发现,我们在走左边的路的时候,已经计算过fib[i-2]的值了,所以在走右边的路的时候,我们就可以不用重复计算它的值,直接拿算出来的数据直接使用即可

所以需要在递归时记录我们计算过的值

int find(int x) {
if (fib[x]) return fib[x];
return fib[x] = (find(x - 1) + find(x - 2)) % 1000;//保存结果
}
cout << find(a) << endl;

但是,由于我们递归的深度可以达到1e6,很多编译器会出现栈溢出的问题,并且占用的内存极大,非必要不采用

(上为记忆化递归,下为递推)

也可以进行尾递归的优化操作

每次递归时,把当前的运算结果(或路径)放在参数里传给下层函数,即栈的覆盖

其中,u为当前层的值,v为上一层的值

int tailfind(int n, int u = 1, int v = 1) {//如果没有传入u,v的值,就默认为1,1
if (n == 1) return u % 1000;
if (n == 2) return v % 1000;
return tailfind(n - 1, v, (u + v) % 1000);
}
cout << tailfind(a) << endl;

可以看见,内存的占用大大降低了,但是仍然不如我们最优的递推操作


完整AC代码:

递推:

#include <iostream>
using namespace std;
const int N = 1e6;
int fib[N + 10];
void pre(void) {
fib[1] = 1, fib[2] = 1;
for (int i = 3; i <= N; i++)
fib[i] = (fib[i - 1] + fib[i - 2]) % 1000;
}
int main()
{
int n;
cin >> n;
pre();
while (n--)
{
int a;
cin >> a;
cout << fib[a] << endl;
}
return 0;
}

记忆化递归:

#include <iostream>
using namespace std;
const int N = 1e6;
int fib[N + 10];
int find(int x) {
if (fib[x]) return fib[x];
return fib[x] = (find(x - 1) + find(x - 2)) % 1000;
}
int main()
{
int n;
cin >> n;
fib[1] = fib[2] = 1;
while (n--)
{
int a;
cin >> a;
cout << find(a) << endl;
}
return 0;
}

尾递归:

#include <iostream>
using namespace std;
const int N = 1e6;
int fib[N + 10];
int tailfind(int n, int u = 1, int v = 1) {
if (n == 1) return u % 1000;
if (n == 2) return v % 1000;
return tailfind(n - 1, v, (u + v) % 1000);
}
int main()
{
int n;
cin >> n;
fib[1] = fib[2] = 1;
while (n--)
{
int a;
cin >> a;
cout << tailfind(a) << endl;
}
return 0;
}
posted @   才瓯  阅读(73)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
点击右上角即可分享
微信分享提示