动态规划(dynamic programming)与分之方法类似,都是经过组合子问题的解来求解原问题。分治方法将问题划分为互不相交的子问题,递归地求解子问题,再将它们的解组合起来,求出原问题的解(如归并排序)。而动态规划应用于子问题重叠的状况,即不一样的子问题具备公共的子子问题(子问题的求解是递归进行的,将其划分为更小的子子问题)。在这种状况下,分治算法会作许多没必要要的工做,它会反复地求解那些公共子子问题。而动态规划算法对每一个子子问题只求解一次,将其解保存在一个表格中,从而无需每次求解一个子子问题时都从新计算,避免了没必要要的计算工做。算法
一般按以下4个步骤来设计一个动态规划算法:数组
1.刻画一个最优解的结构特征。spa
2.递归地定义最优解的值。设计
3.计算最优解的值,一般采用自底向上的方法。code
4.利用计算出的信息构造一个最优解。blog
例子:钢条切割问题排序
描述:给定一段长度为n英寸的钢条和一个价格表pi(i = 1,2,3...,n)求切割钢条方案,使得销售收益rn最大。
递归
钢条价格表内存
长度i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
价格pi | 1 | 5 | 8 | 9 | 10 | 17 | 17 | 20 | 24 | 30 |
若是一个最优解将钢条切割为k段(1≤k≤n),那么最优切割方案 n = i1 + i2 + i3 +...+ ik,获得最大收益 rn = pi1 + pi2 + pi3 + ... + pik。更通常地,对于 rn(n ≥ 1),咱们能够用更短的钢条的最优切割收益来描述它:rn = max(pn , r1 + rn-1 , r2 + rn-2, ..., rn-1 + r1),第一个参数pn对应不切割,直接出售长度为n的钢条的方案。其余n-1个参数对应另外n-1种方案:对每一个i = 1,2,...,n-1,首先将钢条切割为长度为i 和 n -i 的两段,接着求解这两段的最优切割收益ri 和rn-i。因为没法预知哪一种方案会得到最优收益,咱们必须考察全部可能的i,选取其中收益最大者。
table
注意到,为求解规模为n的原问题,咱们先求解形式彻底同样,但规模更小的子问题。即当完成首次切割后,咱们将两段钢条当作两个独立的钢条切割问题实例。经过组合两个关于子问题的最优解,并在全部可能的切割方案中选取组合收益最大者,构成原问题的最优解。动态规划方法仔细安排求解顺序,对每一个子问题只求解一次,并将结果保存下来。是付出额外的内存空间来节省计算时间。有两种等价的实现方法:
一,带备忘的自顶向下法。此方法按天然的递归形式编写过程,但过程会保存每一个子问题的解,当须要一个子问题的解时,过程首先检查是否已经保存过此解。若是是,则直接返回保存的值,从而节省了计算时间;不然,按一般方式计算这个子问题。
二,自底向上法。这种方法通常须要恰当定义子问题"规模"的概念,使得任何子问题的求解只依赖于"更小"的子问题的求解。于是能够将子问题按规模排序,按由小到大的顺序进行求解。当求解某个子问题时,它所依赖的那些更小的子问题都已求解完毕,结果已经保存。
自底向上法代码以下:
1 void bottomUpCutRod(const vector<int> &p,int n , vector<int> &r) 2 { 3 r.resize(n+1); 4 r[0] = 0; 5 int q = -1; 6 7 for(int j = 1; j <= n; ++j){ 8 q = -1; 9 for(int i = 1; i <= j; ++i){ 10 q = std::max(q,p[i] + r[j-i]); 11 } 12 r[j] = q; 13 } 14 }
例子:
1 int main() 2 { 3 int p[] = {-1,1,5,8,9,10,17,17,20,24,30}; 4 vector<int> vp(p,p + sizeof(p)/sizeof(int)); 5 vector<int> result; 6 bottomUpCutRod(vp,9,result); 7 for(int i = 1; i <=9; ++i) 8 cout << result[i] << " "; 9 }
结果保存在result数组中,长度为i的钢条切割的最优收益值为result[i]。
输出: