对于动态规划这类算法思想是,把问题划分为小的子问题,再利用递归或者递推的方式进行求解。但是对于一般的递归类算法,可能会多次相同的求解子问题,这就造成时间上的浪费,所以我们的一种思想就是把在前面求得的子问题储存起来,以便后面要用的时候直接调用即可。
最经典、最简单的动态规划莫过于斐波那契数列,以下就以此为例子展开说明。
实践是检验真理的唯一标准 码上
#include<iostream>
using namespace std;
int F(int n)
{
if (n < 0)return 0;
if (n == 0 || n == 1)return 1;
//设置的数列前两项为1、1,即整个数列为1、1、2、3、5、8……
else return F(n - 1) + F(n - 2);
}
int main()
{
int n = 0;
scanf("%d", &n);
/*可能会有同学疑惑为啥用scanf\printf,cin\cout不香吗?
之前写算法的时候就是因为这个点超时了,所以不香了,就养成了这个习惯 */
printf("%d\n", F(n));
return 0;
}
测试样例 5、15、45
可以看到时间增加得很快,当项数达到100的时候,基本已经很难等到它了(如果大家不信的话,可以copy两段代码分别测试一下),效率很低下。
问题一:为什么求解斐波那契数列算是动态规划?假设,我们要求得第7项的值时,我们要先求得第3、4、5、6项,每一项都是由前面两项相加得来,是由前面的子问题一步一步得出结果,而不是靠什么公式,一下子算出来。
问题二:为什么递归算法时间复杂度会这么高?我们开始读代码,一步步来执行。当求解第3项时我们要用到第1、第2项;当求解第4项时我们要用到第2、第3项;当求解第5项时我们要用到第3、第4项,以此类推……假设我们要求第5项时,可以发现第3项求了两次,第2项求了3次(这还只是求第5项),所以每次求后面项的时候都要把前面所求的都重复再求一遍,造成了时间上的浪费。
试想以下,如果我们把前面求得的项储存起来,就解决了重复计算的问题,就不会造成了时间上的浪费。可以这么理解,动态规划的本质就是用空间来换取时间。
所以我们转到递推算法。
递推算法:
#include<iostream>
using namespace std;
int F(int n)
{
if (n < 0)return 0;
if (n == 0 || n == 1)return 1;
else
{
int f1=1, f2=1, f3=1;
for(int i=2;i<=n;i++)
{
f1=f2;
f2=f3;
f3=f1+f2;
}
return f3;
}
}
int main()
{
int n = 0;
scanf("%d", &n);
printf("%d\n", F(n));
return 0;
}
测试样例:45
可以看到时间减少了很多。我们把后面求得的项赋予前面,即存储起来,因为前面的项不会再被用到,所以可以覆盖,只是用到前面两项而已。
看完本篇的同学算是开始步入动态规划了,喜欢的同学可以关注,希望能帮助到你们!