动态规划是一种非常精妙的算法,他没有固定的写法,极其灵活,常常需要具体问题具体分析。经典模型先接触再慢慢体会。
不要畏惧,多训练,多思考,多总结是学习动态规划的重点。
- 什么是动态规划?
是一种用来解决一类最优化问题的算法思想。
简单来说,就是将一个复杂的问题分解成若干个子问题,通过综合子问题的最优解来得到原问题的最优解。需要注意的是,动态规划会将每个求解过的子问题的解记录下来,这样当下一次碰到同样的问题时,可以直接使用之前记录的结果,而不是重复计算。
一般可以用递归和递推两种写法来实现动态规划,其中递归写法在此处又称为记忆化搜索。
- 动态规划的递归写法:
通过此部分内容的学习,读者应能理解动态规划是如何记录子问题的解,来避免下次遇到相同的子问题时的重复计算 的。
以fibonacci数列为例:
代码参考1:`
int f(int n){
if(n==0||n==1) return n;
else return f(n-1)+f(n-2);
}
事实上,这个递归会涉及很多的重复计算,当n==5时,f5=f3+f4;f4=f3+f2,这时如果不采取措施,f3会被重复计算两次。可以推知,如果n很大,重复计算的次数会很多,实际复杂度会高达O(2^n),每次都会计算f(n-1)+f(n-2)这两个分支,基本不能承受n较大的情况。
为了避免重复,现在使用一维数组dp[n]来记录f(n)的值,初始化为0,如果不为0,则证明已经计算出,则直接计算,而不必重复计算。
参考代码如下:
#include<cstdio>
int dp[40]={0};
int f(int n)
{
if(n==0||n==1) return n;
if(dp[n]!=0) return dp[n];
else{
dp[n]=f(n-1)+f(n-2);
return dp[n];
}
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF){
int x=f(n);
printf("%d\n",x);
}
return 0;
}
这样就把已经计算过的内容记录下来了,通过记忆化搜索,复杂度降为O(n)。
通过上面的栗子,可以引申出一个概念,如果一个问题可以被分解为若干个自问题,且这些子问题会重复出现,那么称这个问题拥有重叠子问题。动态规划通过记录重叠子问题的解,来使下次碰到相同子问题时直接使用之前记录的结果,避免大量重复计算。
因此,一个问题必须拥有重叠子问题,才能使用动态规划去解决。