动态规划算法(一)

动态规划与分治算法的联系与区别

动态规划与分治方法相似,均是通过子问题的解求解原问题。区别是分治算法在求解子问题时会做许多不必要的工作,它会反复求解那些公共子问题;而动态规划算法对每个子子问题都只求解一次,将其解保存在一个表格中,从而无需每次求解子子问题时都重新计算,避免了不必要的计算工作。

应用动态规划来解决一个具体的例子

钢条切割问题:给定一段长度为n英寸的钢条和一个价格表Pi(i=1,2,...n),求切割钢条方案,使得销售收益Rn最大。
长度i12345678910
价格Pi1589101717202430

先说一下整体的求解方法:将钢条从左边切割下长度为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;
}


这是一种直接的自顶向下的递归方法,这种方法效率极低,因为CUT-ROD反复地求解相同的子问题。通过图例来说明此问题:

图中,从父节点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]中。
PRINT-CUT-ROD-SOLUTION(p,n)算法通过调用 EXTENDED-BOTTOM-UP-CUT-ROD(p,n)来计算切割下来的每段钢条的长度s[1...n],最后输出长度为n的钢条的完整的最优切割方案。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值