动态规划问题探究及其Java实现

来源:互联网 发布:js对象深拷贝 编辑:程序博客网 时间:2024/06/06 16:55

问题引入:

     某公司出售的长为i的钢条的价格为p(i)(单位为元),如下表所示。如果有一段长为n的钢条,求如何切割才能使收益最大?

                                              

测试的最优方案:

                                  
                                                       

  1.最容易想到的解法

思路:我们把长度为n的钢条分为i和n-i两部分,1<=i<=10,且不需要再分割,则只需要对n-i求最大收益相加即可。同理对n-i也分为两段,左边的不需要分解,继续分解右边的,直到右边的部分为0。这是一种自顶向下的递归方法。具体代码如下:

public class Guihua {public int cutProfit(int len) { int sum=0; int[] price= {0,1,5,8,9,10,17,17,20,24,30};//长度价格表 if(len==0) return 0; for(int i=1;i<=len && i<price.length;i++) {int tmpsum=price[i]+cutProfit(len-i);//将len长度的钢条切割成两段,递归求解第二段if(tmpsum>sum)sum=tmpsum; } return sum;         }public static void main(String[] args){ long startTime=System.currentTimeMillis(); System.out.println(new Guihua().cutProfit(20)); long endTime=System.currentTimeMillis(); //获取结束时间 System.out.println("程序运行时间: "+(endTime-startTime)+"ms");}}


    运行上述代码,结果与上标一致。但是运行时间很慢,n=20时的运行结果长达363ms。n=50,超时,没有运行出结果。代码之所以效率很低,是应为上述递归方法反复求解相同的子问题。如n=5,需要求n=3,;n=4,又需要求n=3。这时就需要用动态规划算法了。

2.动态规划算法

     对上述问题,运用动态对话算法仔细安排求解顺序,对每个子问题只需要求解一次,并将结果保存下来,下次用到该结果时,只需要直接读取,不需要重复计算。因此动态规划方法时付出额外的内存空间来节省计算时间,时时空权衡的例子。
    动态规划有两种等价的实现方法,分别是带备忘录的自顶向下法自底向上法,以钢条切割问题展示如下:

2.1带备忘录的自顶向下法

    第一种为带备忘录的自顶向下法。此方法仍用递归实现,但是过程会保存子问题 的解(可以保存在数组中)。需要子问题的解时,先检查是否保存该值,如保存直接用;否则,按上面的方法计算。这其实是一种不断分解的方法。在Java实现中,meArray存储计算好的最优子问题的解,由于在递归程序中初始化数组会破坏已经赋值的数组元素,所以将该数组放置函数外面初始化,并用函数调用。

带备忘录的自顶向下法实现:

public class Guihua { int  fromTop(int len,int[] meArray) { int sum=0; int[] price= {0,1,5,8,9,10,17,17,20,24,30};//长度价格表 if(len==0) return 0; if(meArray[len]!=0) //已存,直接使用 return meArray[len]; for(int i=1;i<=len && i<price.length;i++)//前半段只能是1到10,没有大于10的规格的钢条 {int tmpsum=price[i]+fromTop(len-i,meArray);if(tmpsum>sum)sum=tmpsum; }   meArray[len]=sum;return sum;  } public static void main(String[] args){ long startTime=System.currentTimeMillis(); int len=20; int[] meArray=new int[len+1];//记忆数组,记录已经算出的值 System.out.println( new Main().fromTop(len, meArray));  for(int ele:meArray) System.out.print(ele+" "); long endTime=System.currentTimeMillis(); //获取结束时间 System.out.println("程序运行时间: "+(endTime-startTime)+"ms");}}
查看运行结果发现n=20,用时3ms,输出的记忆数组为:0 1 5 8 10 13 17 18 22 25 30 31 35 38 40 43 47 48 52 55 60,程序效率提高。但是程序中没有给出最优解得具体形式。下面给出方法:

2.1.1含有最优方案的带备忘录的自顶向下法

    由上一阶段,我们得到了对不同n对应的最大收益。第一段长度先固定,求第二段的分解方案,如果我们比较出一个较大的第二段长度,就把对应的长度存起来,这样最后存的肯定是收益最大时的其中一段长度,我们把它称作确定段长度。由总长度和确定段长度,二者相减就可以确定下一段长度;然后由下一段长度和它对应的确定长度又可确定下下段长度,然后依次运算,直到相减等于0。可以参考下面的表格:
                                                          

具体实现仍采用递归的方法:

public class Guihua { int fromTopDetail(int len, int[] meArray, int[] detailArray) { int sum=0; int[] price= {0,1,5,8,9,10,17,17,20,24,30};//长度价格表 if(len==0) return 0; if(meArray[len]!=0) // return meArray[len]; for(int i=1;i<=len && i<price.length;i++) {int tmpsum=price[i]+fromTopDetail(len-i,meArray,detailArray);if(tmpsum>sum){sum=tmpsum;detailArray[len]=i;} }   meArray[len]=sum;return sum;}  void getDeatil(int[] methodArray,int index) { //由第一段长度,求解优方案if(methodArray[index]==index){System.out.println(methodArray[index]);return;}elseSystem.out.println(methodArray[index]);getDeatil(methodArray,index-methodArray[index]);}  public static void main(String[] args){ long startTime=System.currentTimeMillis(); int len=9; int[] meArray=new int[len+1];//记忆数组,记录已经算出的值 int[] detailArray=new int[len+1];//记忆数组,记录已经算出的值 System.out.println( new Main().fromTopDetail(len, meArray, detailArray));  new Main().getDeatil(detailArray,detailArray.length-1);  long endTime=System.currentTimeMillis(); //获取结束时间 System.out.println("程序运行时间: "+(endTime-startTime)+"ms");}}

2.2含具体方案的自底向上法


    第二种方法称为自底向上法。这种方法的思路是,任何问题都依赖比它更小的问题的求解。依次将问题按照从小到大的问题求解,当求到某个问题时,它所依赖的更小的问题都已求解完毕,结果已保存。每个子问题只需要求解一次。这其实是一种自小而大组合的方法。具体的实现不采用递归的方式,而是采用循环,用小的子问题的结果依次组合到大的问题,函数返回一个具体方案数组detailArray,就是对每个小于等于n的钢条,存储它的确定长度。由detailArray求解具体方案,和上面方法一样。
具体实现:
public class Guihua { private int[] fromBottomDetail(int len) { int[] price= {0,1,5,8,9,10,17,17,20,24,30};//长度价格表 int[] meArray=new int[len+1];  int[] detailArray=new int[len+1];  int sum=0; for(int i=0;i<=len;i++) { for(int j=0;j<=i && j<price.length;j++) {   if(sum<price[j]+meArray[i-j]) {sum=price[j]+meArray[i-j];       meArray[i]=price[j]+meArray[i-j];    detailArray[i]=j; } } }  System.out.println(sum); return detailArray;}  void getDeatil(int[] methodArray,int index) { //由第一段长度,求解优方案if(methodArray[index]==index){System.out.println(methodArray[index]);return;}elseSystem.out.println(methodArray[index]);getDeatil(methodArray,index-methodArray[index]);}  public static void main(String[] args){ long startTime=System.currentTimeMillis(); int len=7; int[] meArray=new int[len+1];//记忆数组,记录已经算出的值 int[] detailArray=new Guihua().fromBottomDetail(len); new Main().getDeatil(detailArray,detailArray.length-1);  long endTime=System.currentTimeMillis(); //获取结束时间 System.out.println("程序运行时间: "+(endTime-startTime)+"ms");}}


                     

原创粉丝点击