求解斐波那契数列的动态规划方法

斐波那契数列是一个比较经典的数列:0,1,1,2,3,5,8,...特点是除了第一二项,每一项的值等于前边两项的值的和。

记得在学习编程语言的时候,老师用斐波那契数列这个例子来引出“递归”的思想。

先介绍一下用递归如何求解第n个斐波那契数列的值:

#include <iostream>
using namespace std;
double function1(double n) {
	if(n==0)
		return 0;
	if(n==1)
		return 1;
	return function1(n-1)+function1(n-2);
}
这个算法本身没有错,但是有一个问题:当求解的项比较大时(比如第50项,第100项),耗费的时间变得非常多。这涉及到另外一门学科:算法设计与分析,有一个名词叫“时间复杂度”,没听过时间复杂度的读者可以这样简单地理解:一段代码耗费的时间可以表示成一个函数,当测试的数据量增加时耗费的时间也会增加,即这个函数的值会增大,但是增大的趋势也有差别,比如y=x^2增长的速度比y=x增长的速度要大得多。也就是说,y=x^2耗费的时间会比y=x大得多。

按照递归的思想来解决这个问题,以上边的代码为例,其时间复杂度是O(2^n),之所以会这么大,是因为递归的时候会重复计算很多次相同的值。

下面进入正题:用动态规划的思想解决斐波那契数列的问题。

简单说说思想:之前说了,之所以递归会那么耗时,是因为递归的时候重复计算了很多遍已经计算过的值。那么我们可以把每次计算的结果记录下来,等到计算的时候,只要找到要用的两个值,就可以直接相加得到结果。

也就是说,采用一种映射,记录下key-value的值,然后要用n-1和n-2个项的时候,直接取出key=n-1和key=n-2的对应的value。

映射,在C++中有一个叫做map的头文件,但是如果去查一下,就会知道,map<key,value>并没有<int,int>的构造方法(如果想要采用<string,int>也可以考虑).其实还有更简单的方法,不需要用map,直接用数组。开一个int型的数组,数组的下标作为key,值作为value,既避免了复杂的数据结构,又能够达到目的。

代码如下:

void function2(int l) {
	double *a=new double[l];
	for(int i=0;i<l;i++)
		a[i]=0;
	a[0]=0;a[1]=1;
	for(int i=2;i<l;i++) {
		a[i]=a[i-1]+a[i-2];
	}
	cout<<a[l-1]<<endl;
}

参数l是要求解的项的位置(也就是第l项)

这种方法可以把时间复杂度降为O(n).一输入数字就可以很快得到结果了。

运行的main函数:

int main() {
	int l=0;
	cin>>l;
	cout<<"function2:\n";
	function2(l);
	cout<<"function1:\n"<<function1(l-1)<<endl;
	return 0;
}

注意function1(也就是递归函数)传入的参数是l-1,因为对于一个数列,我们都是从第1项开始,但是对于一个数组,其下标是从0开始。

之所以函数中使用的类型是double,是因为当输入的l比较大的时候,int或者long类型均会发生溢出。而main函数先输出function2,是因为当输入的l比较大,运行的时候就可以看到,function2一下子就出来,而function1还要等很久。如果l比较小,耗费的时间差别就不太明显。

运行结果就不放图了,代码如上,有兴趣的读者可以自己去试一试。


  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值