动态规划与分治算法的联系与区别
动态规划与分治方法相似,均是通过子问题的解求解原问题。区别是分治算法在求解子问题时会做许多不必要的工作,它会反复求解那些公共子问题;而动态规划算法对每个子子问题都只求解一次,将其解保存在一个表格中,从而无需每次求解子子问题时都重新计算,避免了不必要的计算工作。
应用动态规划来解决一个具体的例子
钢条切割问题:给定一段长度为n英寸的钢条和一个价格表Pi(i=1,2,...n),求切割钢条方案,使得销售收益Rn最大。
长度i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
价格Pi | 1 | 5 | 8 | 9 | 10 | 17 | 17 | 20 | 24 | 30 |
先说一下整体的求解方法:将钢条从左边切割下长度为i的一段,只对右边剩下的长度为n-i的一段继续进行切割,对左边的一段则不再进行切割。我们可以用公式来描述上述方法
Rn=max(Pi+Rn-i) 1<=i<=n
首先用分治的思想求解最优钢条切割问题
CUT-ROD(p,n)
if n==0
return 0
q=-1
for i=1 to n
q=max(q,p[i]+CUT-ROD(P,n-i))
return q
int cut_rod(vector<int>&v, int len) {
if (len == 0)
return 0;
int q = -1;
for (int i = 1; i <= len; ++i) {
q = max(q, v[i]+cut_rod(v, len - i));
}
return q;
}
int main()
{
vector<int> v = { 0,1,5,8,9,10,17,17,20,24,30 };
int max = cut_rod(v, 9);
cout << max << endl;
system("pause");
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/22979b3120915a78eb48d2092e818310.png)
图中,从父节点s到子节点t的边表示从钢条左端切下长度为s-t的一段,然后继续递归求解剩余的规模为t的子问题。可以看出,在图中有许多相同的子树,比如以1、2为根的子树,在利用前面的算法求解时,每次都要对相同的子问题多次求解,导致效率很低。
使用动态规划方法求解最优钢条切割问题
前面的算法最大的问题就是反复求解相同的子问题,而动态规划方法通过安排求解顺序,做到对每个子问题只求解一次,并将结果保存下来。以后再次需要此子问题的解时,只需查找保存的结果(通过空间换取时间)。
带备忘的自顶向下法
MEMOIZED-CUT-ROD(p,n)
for i=0 to n
r[i]=-1
return MEMOIZED-CUT-AUX(p,n,r)
MEMOIZED-CUT-ROD-AUX(p,n,r)
if r[n]>=0
return r[n]
if n==0
q=0
else q=-10000
for i=1 to n
q=max(q,p[i]+MEMOIZED-CUT-ROD-AUX(p,n-i,r))
r[n]=q
return q
用C++实现如下:
int cut_rod_aux(vector<int> p, int n, vector<int> r) {
if (r[n] > 0)
return r[n];
if (n == 0)
return 0;
int q = -1;
for (int i = 1; i <= n; ++i) {
q = max(q, p[i] + cut_rod_aux(p, n - i, r));
}
r[n] = q;
return q;
}
int cur_rod(vector<int> p, int n) {
vector<int> r(12);
for (int i = 0; i < n; ++i)
r[i] = -1;
return cut_rod_aux(p, n, r);
}
int main()
{
vector<int> v = { 0,1,5,8,9,10,17,17,20,24,30 };
int max = cur_rod(v, 9);
cout << max << endl;
system("pause");
return 0;
}
自底向上的动态规划算法
BOTTOM-UP-CUT-ROD(p,n)
r[0]=0
for j=1 to n //从小打大,
q=-1
for i=1 to j //i是切割点
q=max(q,p[i]+r[j-i]) //计算长度为j时的最大销售收益
r[j]=q //保存长度为j时的最大销售收益
return r[n] //返回长度为n时的最大销售收益
上述方法需要恰当定义子问题“规模”的概念,使得任何子问题的求解都只依赖于更小的子问题的求解。所以通过将子问题按规模排序,按从小到大的顺序进行求解。当求解某个子问题时,它所依赖的那些更小的子问题都已经求解完毕,结果已经保存。这样,每个子问题只需求解一次,当求解一个子问题时,它的所有前提子问题都已求解完成。
求解规模为j的子问题的方法与CUT-ROD所采用的方法相同,只是通过一个数组将r[j]的元素保存下来,直接访问r[j-i]来获得规模为j-i的子问题的解,而不必进行递归调用。
扩充的动态规划算法(可以计算出切割后的钢条的长度)
EXTENDED-BOTTOM-UP-CUT-ROD(p,n)
r[0]=0
for j=1 to n
q=-1
for i=1 to j
if q<p[i]+r[j-i]
q=p[i]+r[j-i]
s[j]=i
r[j]=q
return r and s
PRINT-CUT-ROD-SOLUTION(p,n)
(r,s)=EXTENDED-BOTTOM-UP-CUT-ROD
while n>0
print s[n]
n=n-s[n]
EXTENDED-BOTTOM-UP-CUT-ROD(p,n)算法是在BOTTOM-UP-CUT-ROD上的略微改进,新创建数组s,在求解规模为j的子问题时将第一段钢条的最优切割长度i保存在s[j]中。