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

问题引入:

     某公司出售的长为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;
			}
			else
				System.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;
			}
			else
				System.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");
		}
}


                     

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值