动态规划-递归与递推写法
动态规划
动态规划(Dynamic Programming, DP)是一种用来解决一类最优化问题的算法思想。
简单来说,动态规划是将一个复杂的问题分解成若干个子问题,通过综合子问题的最优解来得到原问题的最优解。动态规划会把每个求解过的子问题的解记录下来,这样当下一次碰到同样的子问题时,就可以直接使用之前记录的结果,而不是重复计算。
一般可以使用递归或者递推的方法的写法来实现动态规划,其中递归写法又称为 记忆化搜索。
递归写法(自顶向下)
动态规划记录子问题,避免下次遇到相同的子问题时重复计算。
以斐波那契数列为例,F0 = 1,F1 = 2,Fn = Fn-1 + Fn-2 。
递归写法
int F(int n) {
if(n == 0 || n == 1) return 1;
else return F(n-1) + F(n-2);
}
事实上这个递归会重复计算很多次,例如F(5) = F(4) + F(3), F(4) = F(3) + F(2)。这个时候如果不采取措施,F(3) 会被计算两次,如果 n 很大,重复计算的次数很大,
为了避免重复计算,可以开一个一维数组 dp[],用以保存已经计算过的结果,其中 dp[n] 记录 F(n) 的结果,并用 dp[n] = -1 表示还没计算过。
int dp[maxn];
int F(int n) {
if(n == 0 || n == 1) return 1;
if(dp[n] != -1) return dp[n];
else {
dp[n] = F(n-1) + F(n-2); // 计算 F(n),并且保存在 dp[n] 中
return dp[n];
}
}
递推写法(自底向上)
还是以我们刚才的求解斐波那契数列为例,递推写法就应该从底部向上求解
int dp[maxn];
int F(int n) {
dp[0] = dp[1] = 1;
for(int i = 2; i <= n; i++) {
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n];
}
递推写法也就是从子问题开始求解,一直向上求解直到我们要求解的问题被解决。
核心思想
如果一个问题的最优解可以由其子问题的最优解有效的构造出来,那么称这个问题拥有 最优子结构(Optimal Substructure)。最优子结构保证了动态规划中原问题的最优解可以由子问题的最优解推导而来。
因此,一个问题必须拥有最优子结构和重叠子问题才能使用动态规划去解决。
这两个概念的区别在于:
- 分治与动态规划。
- 分治和DP都是分解为子问题求解,但是分治分解的问题是不重叠的,但是动态规划解决的问题具有重叠子问题。而且分治解决的问题不一定是最优化问题,而动态规划解决的问题一定是最优化问题
- 贪心与动态规划
- 贪心和动态规划都必须要有最优子结构。二者的计算方式的区别在于,贪心使用的计算方式类似于自顶向下,但是并不是等待子问题求解完毕后再选择一个,而是通过一种策略直接选择一个子问题去求解,没被选择的问题就直接抛弃了。所以不一定能够取得最优解。而动态规划总是会考虑所有的子问题,而且选择结果更优的子问题,对于暂时没有被继承的子问题,由于重叠子问题的存在,后期还可能再会选择它们,因此还有机会成为全局最优的一部分,不需要放弃。