钢条切割问题
现有一段长度为n英寸的钢条和一个价格表
pi
,求切割方案使销售利益最大
rn
最大

长度为n英寸的钢条共有
2n?1
种不同的切割方案,因为可以每个整英寸的位置都可以决定切割或者不切割。
为了得到
rn
最大,可以把这个问题分成子问题求解,先切一刀,再考虑余下的部分的最大收益即求
rn
=max{
pk+rn?k
}(k=1,2,3…n-1),
pk
部分不进行继续切割,直接作为一个整体售出 ;
rn?k
部分继续切割,考虑所有的情况,分成子问题。
求出所有k值对应的收益最大者作为
rn
<??http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPtKy09C/ycTcsru9+NDQyM66zsfQuO7A+9Lm1+6087y0PG5vYnI+cm49cG48L25vYnI+PC9wPg0KPHA+PHN0cm9uZz7X28nPPC9zdHJvbmc+o7ogPG5vYnI+cm49bWF4KHBpK3JuP2kpPC9ub2JyPiAoaT0xLDIsMyZoZWxsaXA7biAsIDxub2JyPnIwPTA8L25vYnI+KTwvcD4NCjxoMSBpZD0="动态规划求解">动态规划求解
考虑上面的算法实现:
rn=max(pi+rn?i)
(i=1,2,3…n ,
r0=0
),为了计算出
rn
必须计算出所有情况的切割方法,即有n个分支,第i个分支又对应着i个子分支,所以若采用递归调用计算所有情况,调用次数将是
2n
次。运行时间为n的指数函数。
上面的分析知,其中重复计算了很多次子问题,如果有个好的计算顺序,子问题先计算一次,把结果保存下来,下次再遇到这个子问题,只需要查表即可。动态规划方法是付出额外的内存空间来节省计算时间。
带备忘的自顶向下法
仍采用递归的方法,但过程中保存已求解过子问题的解,当需要一个子问题的解时,首先检查是否已经保存过此解。
int Memoized_Cut_Rod(int *p,int n)
{
int i=0;
int * r = (int *)malloc((n+1)*sizeof(int)); //分配空间,用来存储已经计算过的子问题
for (i=0;i<=n;i++) //初始化
r[i] = -1;
return Memoized_Cut_Rod_Aux(p,n,r);
}
int Memoized_Cut_Rod_Aux(int *p,int n,int *r)
{
int q= -1,i;
if (r[n] >=0) //首先检查是否已经计算过,若有,直接返回
return r[n];
if (n == 0) //钢条长度为0,收益为0
q=0;
else
{
q = -1;
for (i=1;i<=n;i++)
{
q=Max(q,p[i]+Memoized_Cut_Rod_Aux(p,n-i,r)); //自顶向下递归
}
r[n] = q; //保存子问题的值
}
return q; //返回最大的收益
}
自底向上法
把子问题按规模排序,按由小到大的顺序进行求解,当求解某个问题时,它所依赖的子问题已经被计算过,结果已经保存,这样每个子问题只需要计算一次。
int Bottom_Up_Cut_Rod(int *p,int n)
{
int * r = (int *)malloc((n+1)*sizeof(int)); //分配空间,用来存储已经计算过的子问题
int i,j,q=-1;
r[0]=0;
for (j=1;j<=n;j++)
{
q=-1;
for(i=1;i<=j;i++)
{
q=Max(q,p[i]+r[j-i]); //自底向上,由小到大求解子问题
}
r[j]=q; //保存求解后的结果
}
return r[n];
}
重构解
上面的代码是返回的最大收益,并没有返回具体切割方法。下面代码保存了每种尺寸,对应的左边部分大小(即作为整体售出部分)
int Extended_Bottom_Up_Cut_Rod(int *p,int n)
{
int * r = (int *)malloc((n+1)*sizeof(int));
int i,j,q=-1;
s = (int *)malloc((n+1)*sizeof(int)); //保存每次切割前左边部分剩余尺寸
s[0]=0;
r[0]=0;
for (j=1;j<=n;j++)
{
q=-1;
for(i=1;i<=j;i++)
{
if (p[i]+r[j-i] > q)
{
s[j] = i; //保存左边部分(作为整体售出的部分)
q = p[i] + r[j-i];
}
}
r[j]=q;
}
return r[n];
}
输入钢条的具体切割方法:
void Print_Cut_Rod_Solution(int *p,int n)
{
while(n>0)
{
printf("%d ",s[n]); //循环输出尺寸为n的钢条切割方法
n=n-s[n];
}
}
例程
/************************************************************************
CSDN 勿在浮沙筑高台
http://blog.csdn.net/luoshixian099
算法导论--动态规划(钢条切割)
2015年6月2日
************************************************************************/
#include
#include
int *s=NULL; int Memoized_Cut_Rod_Aux(int *p,int n,int *r); int Max(int a,int b) { return a>b?a:b; } int Memoized_Cut_Rod(int *p,int n) { int i=0; int * r = (int *)malloc((n+1)*sizeof(int)); //分配空间,用来存储已经计算过的子问题 for (i=0;i<=n;i++) //初始化 r[i] = -1; return Memoized_Cut_Rod_Aux(p,n,r); } int Memoized_Cut_Rod_Aux(int *p,int n,int *r) { int q= -1,i; if (r[n] >=0) //首先检查是否已经计算过,若有,直接返回 return r[n]; if (n == 0) //钢条长度为0,收益为0 q=0; else { q = -1; for (i=1;i<=n;i++) { q=Max(q,p[i]+Memoized_Cut_Rod_Aux(p,n-i,r)); } r[n] = q; //保存子问题的值 } return q; //返回最大的收益 } int Bottom_Up_Cut_Rod(int *p,int n) { int * r = (int *)malloc((n+1)*sizeof(int)); //分配空间,用来存储已经计算过的子问题 int i,j,q=-1; r[0]=0; for (j=1;j<=n;j++) { q=-1; for(i=1;i<=j;i++) { q=Max(q,p[i]+r[j-i]); //自底向上,由小到大求解子问题 } r[j]=q; //保存求解后的结果 } return r[n]; } int Extended_Bottom_Up_Cut_Rod(int *p,int n) { int * r = (int *)malloc((n+1)*sizeof(int)); int i,j,q=-1; s = (int *)malloc((n+1)*sizeof(int)); //保存每次切割前左边部分剩余尺寸 s[0]=0; r[0]=0; for (j=1;j<=n;j++) { q=-1; for(i=1;i<=j;i++) { if (p[i]+r[j-i] > q) { s[j] = i; //保存左边部分(作为整体售出的部分) q = p[i] + r[j-i]; } } r[j]=q; } return r[n]; } void Print_Cut_Rod_Solution(int *p,int n) { while(n>0) { printf("%d ",s[n]); //循环输出尺寸为n的钢条切割方法 n=n-s[n]; } } void main() { int p[]={0,1,5,8,9,10,17,17,20,24,30}; int i; for (i=1;i<=10;i++) { //printf("%d\n",Memoized_Cut_Rod(p,i)); printf("尺寸为%d的最大收益:%d ",i,Extended_Bottom_Up_Cut_Rod(p,i)); // printf("%d \n",s[i]); printf("切割方法:"); Print_Cut_Rod_Solution(p,i); free(s); s=NULL; printf("\n"); } }
