斐波那契数列的优化方案

题目描述

根据之前的斐波那契数列简单版,可以看出,我们使用递归只能在一秒内算出第 \(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 @ 2024-12-18 17:24  才瓯  阅读(138)  评论(0)    收藏  举报