动态规划算法
动态规划的定义
动态规划(Dynamic Programming, DP) 是一种用来解决一类最优化问题的算法思想。简单来说,动态规划将一个 复杂的问题分解成若千个子问题,通过综合子问题的最优解来得到原问题的最优解。需要注意的是,动态规划会将每个求解过的子问题的解记录下来,这样当下一次碰到同样的子问题时,就可以直接使用之前记录的结果,而不是重复计算。注意:虽然动态规划采用这种方式来提高计算效率,但不能说这种做法就是动态规划的核心^(后面会说明这一点)。 一般可以使用递归或者递推的写法来实现动态规划,其中递归写法在此处又称作记忆化搜索。
动态规划法求解的特点
- 最优子结构
如果一个问题的最优解中包含了子问题的最优解,说明该问题具有最优子解决。 - 重叠子问题
动态规划算法的子问题不是相互独立的,而是有公共的部分,即有重叠子问题。
动态规划法求解的思路
- 穷举分析
- 确定边界
- 找出规律,确定最优子结构
以斐波那契(Fibonacci) 数列为例:
1. 穷举分析
n | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
f(n) | 1 | 1 | f(1)+f(0) | f(2)+f(1) | f(3)+f(2) | f(4)+f(3) | f(5)+f(4) |
2. 确定边界
f(0)=1
f(1)=1
f(2)=f(0)+f(1)
…
f(n)=f(n-2)+f(n-1)
所以边界为f(0)=1,f(1)=1
3. 找规律,确定最优子结构
n>=3时,已经呈现出规律 f(n) = f(n-1) + f(n-2) ,因此,f(n-1)和f(n-2) 称为 f(n) 的最优子结构。什么是最优子结构?有这么一个解释:
一道动态规划问题,其实就是一个递推问题。假设当前决策结果是f(n),则最优子结构就是要让 f(n-k) 最>优,最优子结构性质就是能让转移到n的状态是最优的,并且与后面的决策没有关系,即让后面的决策安心>地使用前面的局部最优解的一种性质”
动态规划的递归写法
以斐波那契(Fibonacci) 数列为例,按下面的代码来计算的:
暴力递归
int F(int n){
if(n==0||n==1)
return 1;
else
return F(n-1)+F(n-2);
}
这个递归会涉及很多重复的计算。如图所示,当n=5时,可以得到F(5)= F(4)+ F(3), 接下来在计算F(4)时又会有F(4)= F(3)+ F(2)。 这时候如果不采取措施,F(3)将会被计算两次。可以推知,如果n很大,重复计算的次数将难以想象。事实上,由于没有及时保存中间计算的结果,实际复杂度会高达O(2^n),即每次都会计算F(n- 1)和F(n - 2)这两个分支,基本不能承受n较大的情况。
为了避免重复计算,可以开一个一-维数组dp,用以保存已经计算过的结果中dp[n]记录F(n)的结果,并用dp[n]=-1表示F(n)当前还没被计算过。样就把已经计算过的内容记录了下来,于是当下次再碰到需要计算相同的内容时,就能直接使用上次计算的结果,剪去已经有结果的树枝,这可以省去大半无效计算,而这也是记忆化搜索这个名字的由来。
int dp[MAXN];
memset(a,-1,sizeof(a));//注意memset在<string>中,且只能赋值0或-1;
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);
return dp[n];
}
}
特点:自顶向下
动态规划的迭代写法
以斐波那契(Fibonacci) 数列为例,按下面的代码来计算的:
int FibonacciD(int num) {
if(num <= 0) {
return 0;
}
if(num == 1 || num == 2) {
return 1;
}
int first = 1,second =1,third = 0;
for(int i = 3; i<= num ;i++) {
third = first + second;
first = second;
second = third;
}
return third;
}
特点:自底向上
动态规划的算法与其他算法的区别
分治法与动态规划
分治和动态规划都是将问题分解为子问题,然后合并子问题的解得到原问题的解。但是不同的是,分治法分解出的子问题是不重叠的,相互独立,因此分治法解决的问题不拥有重叠子问题,而动态规划解决的问题拥有重叠子问题。例如,归并排序和快速排序都是分别处理左序列和右序列,然后将左右序列的结果合并,过程中不出现重叠子问题,因此它们使用的都是分治法。另外,分治法解决的问题不一定是最优化问题,而动态规划解决的问题一-定是最优化问题。
贪心算法与动态规划
贪心和动态规划都要求原问题必须拥有最优子结构。二者的区别在于,贪心法采用的计算方式类似于上面介绍的“自顶向下”,但是并不等待子问题求解完毕后再选择使用哪一个,而是通过一种策略直接选择-一个子 问题去求解,没被选择的子问题就不去求解了,直接抛弃。也就是说,它总是只在上一步选择的基础上继续选择,因此整个过程以一"种单链的流水方式进行,显然这种所谓“最优选择”的正确性需要用归纳法证明。例如对数塔问题而言,贪心法从最上层开始,每次选择左下和右下两个数字中较大的一个,一直到最底层得到最后结果,显然这不一定可以得到最优解。而动态规划不管是采用自底向上还是自顶向下的计算方式,都是从边界开始向上得到目标问题的解。也就是说,它总是会考虑所有子问题,并选择继承能得到最优结果的那个,对暂时没被继承的子问题,由于重叠子问题的存在,后期可能会再次考虑它们,因此还有机会成为全局最优的一部分,不需要放弃。所以贪心是一种壮士断腕的决策,只要进行了选择,就不后悔;动态规划则要看哪个选择笑到了最后,暂时的领先说明不了什么。