算法导论第十五章动态规划

本文详细介绍了动态规划在解决钢条切割问题和矩阵链乘法问题中的应用。针对钢条切割,通过动态规划求解最大收益的切割方案,并给出重构解以返回具体方案。矩阵链乘法问题中,通过动态规划寻找最优括号化方案,减少乘法运算次数。同时,文中探讨了贪心策略在这些问题中的局限性,证明了动态规划的有效性和最优子结构的重要性。
摘要由CSDN通过智能技术生成

概述

1.动态规划是通过组合子问题的解而解决原问题的。
2.动态规划适用于子问题不是独立的情况,也就是各子问题的包含公共的子子问题。
3.动态规划对每个子问题只求解一次,将其结果保存在一张表中。
4.动态规划的设计步骤:a.描述最优解的结构b.递归定义最优解的值c.按自底向上的方式计算最优觖的值d.由计算出的结构构造一个最优解

15.1钢条切割

钢条切割问题:给定定长的钢条和价格表,求切割方案,使得收益最大。如果n英寸的钢条的价格足够大,则不需要切割。

代码如下:

//朴素递归求解钢条切割收益,由于递归过程中反复求解同一个子问题,所以导致递归次数呈指数级增长,运行时间也过长。
#include <iostream>
using namespace std;
int Max(int a,int b)
{
	return a>b?a:b;
}
int CUT_ROD(int p[],int n)
{
  if (n==0)
  {
	  return 0;
  }
  int q=-0x7fffffff;
  for (int i=0;i<n;i++)
  {
	  q=Max(q,p[i]+CUT_ROD(p,n-i-1));
  }
  return q;
}
void main()
{
	const int n=10;
    int p[n]={1,5,8,9,10,17,17,20,24,30};
	cout<<CUT_ROD(p,4);
}

使用动态规划方法求解最优钢条切割问题

动态规划有两种等价实现方式:

① 带备忘的自顶向下法,用自然的递归方式,但是过程会保存每个子问题的解。节省了递归时间。

②自底向上法,需要恰当定义子问题“规模”,使其从小到大顺序求解。求解问题时,其子问题总是先解决了。

代码如下:

//下面在求解钢条切割过程中,可能重复求解的子问题都保存起来,使所有子问题仅求解一次,这样运行时间会变为多项式时间。
#if 0
#include <iostream>
using namespace std;
int Max(int a,int b)
{
	return a>b?a:b;
}
int MEMOIZED_CUT_ROD_AUX(int p[],int n,int r[])
{
	static int q;
	if (r[n]>=0)
	{//保存已求过的值。
		return r[n];
	}
	if (n==0)
	{
	  q=0;
	} 
	else
	{
		q=-0x7fffffff;
		for (int i=0;i<n;i++)
		{
			q=Max(q,p[i]+MEMOIZED_CUT_ROD_AUX(p,n-i-1,r));
		}
	}
	r[n]=q;
	return q;
}
int MEMOIZED_CUT_ROD(int p[],int n)
{//方法①自顶向下的动态规划方案
	int *r=new int[n];
	for (int i=0;i<n;i++)
	{
		r[i]=-0x7fffffff;
	}
	return MEMOIZED_CUT_ROD_AUX(p,n,r);
}
int BOTTOM_UP_CUT_ROD(int p[],int n)
{//方法②自底向下的动态规划方案
   int *r=new int[n];
   r[0]=0;
   for (int j=0;j<n;j++)
   {
	   int q=-0x7fffffff;
	   //int q=p[j+1];
	   for (int i=0;i<=j;i++)
	   {
		   q=Max(q,p[i]+r[j-i]);
	   }
	   r[j+1]=q;
   }
   return r[n];
}
void main()
{
	const int n=10;
	int p[10]={1,5,8,9,10,17,17,20,24,30};
	cout<<MEMOIZED_CUT_ROD(p,9)<<endl;
	cout<<BOTTOM_UP_CUT_ROD(p,9)<<endl;
}

子问题图

子问题图规模可以确定动态规划算法的运行时间。其运行时间与图的顶点和边的数量呈线性关系。

重构解:对于钢条切割方案,我们上文仅求最大收益,没有返回对应方案,所以下面代码是既返回收益,同时也返回方案。

//既返回收益,也返回最佳方案。
#include <iostream>
using namespace std;
struct array
{
	int r;//代表最大收益
	int s;//代表切割方案
};
struct array *EXTENDED_BOTTOM_UP_CUT_ROD(int p[],int n)
{
   struct array *rs=new struct array[n];
   rs[0].r=0;
   int q;
   for (int j=0;j<n;j++)
   {
       q=-0x7fffffff;
	   //int q=p[j+1];
	   for (int i=0;i<=j;i++)
	   {
		   if (q<p[i]+rs[j-i].r)
		   {
			   q=p[i]+rs[j-i].r;
			    rs[j+1].s=i+1;
		   }
	   }
	   rs[j+1].r=q;
   }
   return rs+n;
}
void PRINT_CUT_ROD_SOLUTION(int p[],int n)
{
   struct array *rs=EXTENDED_BOTTOM_UP_CUT_ROD(p,n);
   while (n>0)
   {
	   cout<<(*rs).s<<" ";
	   n=n-(*rs).s;
	   rs=rs-(*rs).s;
   }
}
void main()
{
   const int n=10;
   int p[10]={1,5,8,9,10,17,17,20,24,30};
   cout<<(*EXTENDED_BOTTOM_UP_CUT_ROD(p,4)).r<<endl;
   PRINT_CUT_ROD_SOLUTION(p,4);
}

练习:

15.1-1由公式(15.3)和初始条件T(0)=1,证明公式(15.4)成立。

T(n)=1+T(0)+T(1)+...T(n-1)  则有T(n-1)=1+T(0)+T(1)+....+T(n-2) 所以T(n)=2*T(n-1) ,所以根据这个递归式有:(T(1)/T(0))*(T(2)/T(1))........T(n)/T(n-1)=T(n)/T(0)=2^n  因为T(0)=1

所以T(n)=2^n成立。

15.1-2 举反例证明下面的“贪心”策略不能保证总是得到最优切割方案。定义长度为i的钢条的密度pi/i,即每英寸的价值。贪心策略将长度为n的钢条切割下长度为i(1<i<n)的一段,其密度最高。接下来继续使用相同的策略切割长度为n-i的剩余部分

n=1 密度为1,n=2,密度为2.5 ,n=3 密度为2.7 n=4 密度为2.25 那么如果我们有一条4英寸的钢条,从密度最高处切割,那么就是从n=3处切割,收益为8,剩余的部分为n=1,收益为1,总收益为8+1=9. 但是如果从n=2处切割呢,两段都为2英寸,那么总收益就是5+5=10>9 但是这不是密度最高处。所以题目所给方案不能确定最大收益。

15.1-3 我们对钢条切割问题进行一点修改,除了切割下的钢条段具有不同价格pi外,每次切割还要付出固定的成本c.这样,切割方案的收益就等于钢条段价格之和减去切割的成本。设计一个动态规划算法解决修改后的钢条切割问题。

代码如下:此代码还记录的最优方案解

//15.1-3带有固定切割成本的钢条切割方案
#if 0
#include <iostream>
using namespace std;
int Max(int a,int b)
{
	return a>b?a:b;
}
struct array
{
	int r;//代表最大收益
	int s;//代表切割方案
};
struct array *EXTENDED_BOTTOM_UP_CUT_ROD(int p[],int n)
{
	struct array *rs=new struct array[n];
	rs[0].r=0;
	int q;
	for (int j=0;j<n;j++)
	{
		int flag=1;//哨兵为1代表无切割。
        q=p[j];
		for (int i=0;i<j;i++)
		{
			//q=Max(q,p[i]+rs[j-i].r-1);
			if(q<=p[i]+rs[j-i].r-1)//切割固定成本c=1
			{
				q=p[i]+rs[j-i].r-1;
				rs[j+1].s=i+1;
			    flag=0;//哨兵为0代表有切割。
			}
		}
		if (j==i)//i=j代表无切割
		{
			if (q<=p[i]+rs[j-i].r&&flag)
			{//无切割时注意切割方案就等于钢条长度。
				rs[j+1].s=i+1;
			}
		}
		rs[j+1].r=q;
	}
	return rs+n;
}
void PRINT_CUT_ROD_SOLUTION(int p[],int n)
{
	struct array *rs=EXTENDED_BOTTOM_UP_CUT_ROD(p,n);
	while (n>0)
	{
		cout<<(*rs).s<<" ";
		n=n-(*rs).s;
		rs=rs-(*rs).s;
	}
}
void main()
{
	const int n=10;
	int p[10]={1,5,8,9,10,17,17,20,24,30};
	cout<<(*EXTENDED_BOTTOM_UP_CUT_ROD(p,10)).r<<endl;
	PRINT_CUT_ROD_SOLUTION(p,10);
}

15.1-4 修改MEMOIZED-CUT-ROD,使之不仅返回最优收益值,还返回切割方案。

//15.1-4动态规划法递归求解最优钢条切割问题。不仅返回收益还返回切割方案。
#include <iostream>
using namespace std;
struct array
{
	int r;//代表最大收益
	int s;//代表切割方案
};
int Max(int a,int b)
{
	return a>b?a:b;
}
struct array *MEMOIZED_CUT_ROD_AUX(int p[],int n,struct array rs[])
{
	static int q;
	if (rs[n].r>=0)
	{
		return rs+n;
	}
	if (n==0)
	{
		q=0;
	} 
	else
	{
		q=-0x7fffffff;
		for (int i=0;i<n;i++)
		{
			int t=p[i]+MEMOIZED_CUT_ROD_AUX(p,n-i-1,rs)->r;
			if (q<t)
			{
				q=t;
				rs[n].s=i+1;//n相当于上面迭代里的j+1,i+1和上面迭代一样。
			}
		}
	}
	rs[n].r=q;
	return rs+n;
}
struct array *MEMOIZED_CUT_ROD(int p[],int n)
{
	struct array *rs=new struct array [n];
	for (int i=0;i<=n;i++)
	{
		rs[i].r=-0x7fffffff;
	}
	return MEMOIZED_CUT_ROD_AUX(p,n,rs);
}
void PRINT_CUT_ROD_SOLUTION(int p[],int n)
{
	struct array *rs=MEMOIZED_CUT_ROD(p,n);
	cout<<"最大收益:"<<rs->r<<endl;
	cout<<"切割方案:";
	while (n>0)
	{
		cout<<(*rs).s<<" ";
		n=n-(*rs).s;
		rs=rs-(*rs).s;
	}
}
voi
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值