钢条切割问题:
某公司购买长钢条,将其切割为短钢条,价格案例如下:
长度i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
价格Pi | 1 | 5 | 8 | 9 | 10 | 17 | 17 | 20 | 24 | 30 |
给定一个长度为n英寸的钢条,怎么切割使得销售收益最大。
最优子结构:问题的最优解由相关子问题的最优解组合而成,而这些子问题可以独立求解。
钢条切割的简单递归方法:我们将钢条从左边切割下长度为i的一段,只对右边剩下的长度为n-i的一段继续进行切割(递归求解),对左边的一段则不再进行切割。
即问题分解的方式为:将长度为n的钢条分解为左边开始一段,以及剩余部分 继续分解的结果。
动态规划有两种等价的实现方法:
1、带备忘的自顶向下法:此方法扔按自然的递归形式编写过程,但过程会保存每个子问题的解,通常保存在一个数组或散列表中。当需要一个问题的解时,过程首先检查是否已经保存过此解。如果是,则返回保存的值,从而节省了计算时间;否则,按通常方式计算这个子问题,“带备忘”的意思就是记住了之前计算的结果
public static int memoized_cut_rod(int[] p,int n)
{
int result;
int[] res=new int[n+1]; //res备忘数组
for(int i=0;i<res.length;i++)
{
res[i]=-1;
}
result=cut_rod(p, n, res);
return result;
}
public static int cut_rod(int[] p,int n,int[] res)
{
int q=-1;
if(n==0)
q=0;
if(res[n]>=0)
return res[n];
else
{
for(int i=1;i<=n;i++)
{
q=Math.max(q, p[i]+cut_rod(p, n-i, res));
}
}
res[n]=q;
return q;
}
2、自底向上法:这种方法一般需要恰当定义子问题“规模”的概念,使得任何子问题的求解都只依赖于“更小的”子问题的求解,因而我们可以将子问题按规模排序,按由小到大的顺序进行求解。当求解某个子问题时,它所依赖的更小的子问题已经求解完毕,结果已经保存。每个子问题只需求解一次,当我们求解它(也就是第一次遇到它)时,它的所有前提子问题都已求解完成。
public static int bottom_up_cut_rod(int[] p,int n)
{
int[] res=new int[n+1];
res[0]=0;
int q=-1;
for(int i=1;i<=n;i++)
{
res[i]=-9999;
for(int j=1;j<=i;j++)
{
q=Math.max(q, p[j]+res[i-j]);
}
res[i]=q;
}
return res[n];
}
两种方法得到的算法具有相同的渐进运行时间,仅有的差异是在某些特殊情况下,自顶向下的方法并未真正递归考察所有的的子问题,由于没有频繁的递归函数调用的开销,自底向上方法的时间复杂性函数通常具有更小的系数。
代码汇总:
package lianxi;
public class dp_cutrod {
public static int memoized_cut_rod(int[] p,int n)
{
int result;
int[] res=new int[n+1]; //res备忘数组
for(int i=0;i<res.length;i++)
{
res[i]=-1;
}
result=cut_rod(p, n, res);
return result;
}
public static int cut_rod(int[] p,int n,int[] res)
{
int q=-1;
if(n==0)
q=0;
if(res[n]>=0)
return res[n];
else
{
for(int i=1;i<=n;i++)
{
q=Math.max(q, p[i]+cut_rod(p, n-i, res));
}
}
res[n]=q;
return q;
}
public static int bottom_up_cut_rod(int[] p,int n)
{
int[] res=new int[n+1];
res[0]=0;
int q=-1;
for(int i=1;i<=n;i++)
{
res[i]=-9999;
for(int j=1;j<=i;j++)
{
q=Math.max(q, p[j]+res[i-j]);
}
res[i]=q;
}
return res[n];
}
public static void main(String[] args)
{
int[] p={0,1,5,8,9,10,17,17,20,24,30};
int n=9;
int result=memoized_cut_rod(p, n);//调用带备忘的自顶向下法
int result2=bottom_up_cut_rod(p, n);//调用自底向上法
System.out.print(result);
System.out.print(result2);
}
}
输出结果为:25