什么是动态规划?
一、基本思想
态规划算法的基本思想与分治法类似,都是将问题大问题拆分为小问题,通过小问题的求解来得到最后的解。
与分治法不同的是,分治法是分而治之,分治法将大问题拆分为相同性质的子问题,最后合并子问题的解来构成最终解。
而动态规划是,将子问题拆解后,按顺序求解子问题,前面阶段的求解为后一阶段提供有用信息,通过动态的选择来到达最终解。
用图来表示就是如下所示:
二、适用情况
(1)最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。
(2)无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
(3)有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)
----摘自百度百科
三、求解步骤
动态规划中有三个非常重要的概念:最优子结构、边界、状态转移公式。
最优子结构:
最优子结构指的是,问题的最优解包含子问题的最优解。反过来说就是,我们可以通过子问题的最优解,推导出问题的最优解。
边界:就是问题的出口。
状态转移公式:动态规划问题的这一阶段的最优解是可以通过前面阶段的解和上一阶段的决策推导出来的。这个推导过程就是一个状态转移公式
我们通常按照如下4个步骤设计一个动态规划算法:
1.刻画一个最优解的结构特征
2.递归地定义最优解的值
3.计算最优解的值,通常采用自底向上的方法(采用一张表格记录之前的状态)
4.利用计算出的信息构造一个最优解
我们之前的硬币找零问题和背包问题 也是一样的求解步骤。以硬币找零问题为例:
首先,面对一枚新的硬币,我们有两个选择:使用 和 不使用。构成当前阶段的最优解 = min{使用这枚硬币的解, 不使用这枚硬币的解} ---- (1.刻画一个最优解的结构特征)
然后,我们就得到转移方程 Value(i) = min {Value(i-1), Value(s-c[i])) + 1} ---- (2.递归地定义最优解的值)
之后我们从找零1角开始算起,一直到达我们想要找零的数目。中间的计算状态我们都采用一张表进行记录。 ---- (3.计算最优解的值,通常采用自底向上的方法(采用一张表格记录之前的状态))
最后,我们通过之前的计算得到找零六角的最优解。(4.利用计算出的信息构造一个最优解)
我的经验是,面对一个问题,首先判断它是否是动态规划问题(1.最优子结构 2.无后效性 3.重叠子问题),
然后分析选择和不选择当前元素的状态,写出状态转移方程,
之后根据条件或者现实状况找到边界条件,
最后列出表格,一步步计算各个阶段的状态和最优解,根据表格就能得到我们需要的最优解。
动态规划的关键在于阶段的划分(子问题的分解)和 找到状态转移方程。
四、一个🌰
经典问题,求图的最短路径问题。
如下图所示,找出vo到其余各点的最短路径。
假设有点Vx,由图我们可以知道,点V0到Vx的最短距离(设为path(0,x,i))就是前一阶段计算的点V0到Vx的最短距离path(0,x,i-1)与 V0经过中转点Vs到达Vx的距离(也就是path(0,s,i) + direct(s,x))
写成公式就是,path(0,x,i) = MIN{ path(0,x,i-1), path(0,s,i) + direct(s,x) }. 这个就是我们的转移方程。
根据转移方程,我们以各个顶点为中转点,分别计算V0到各个点的最短距离,列出来下面的表格,最后一列就是v0到各点的最短距离。
用代码写一下过程如下:
public static int[] path(int [][]graph){ int n = graph[0].length; int[] path = new int[n]; //初始化 path 数组 for(int i=0; i<n; i++){ if(i == 0){ path[i] = MAX; continue; } path[i] = graph[0][i]; } for(int i=1; i<n; i++){ for(int j=0; j<n; j++){ //比较经过中转 i 和 不经过中转点 i 的大小 if(path[i]+graph[i][j] < path[j]){ path[j] = path[i]+graph[i][j]; } } } return path; }