切割钢条最大利润问题

问题

公司有购买长度为n(我们假设n不超过表格所列的最长长度,即n<=10)的钢条,将其切割成长度为i的钢条出售,长度为i的钢条的出售价格为p[i]。钢条的长度为整数,求切割方案,使得销售利益最大。

长度价格
11
25
38
49
510
617
717
820
924
1030

输入:

int[] p  //长度为i的钢条价格为p[i]
int n //总长度n<=p.length

输出:

  int q//最大利润

解决方法1——朴素递归法

设长度为n的钢条,收益最大的情况要么是直接出售,不进行切割,要么是进行切割,在切割方案中选择一个最大的,所以最大利润为:

r=max{p[n],r1+r(n-1),r2+r(n-2)...r(n-1)+r1}

我们可以计算出所有切割的方法所获得利润,然后取最大值。
普通的递归方法解决:

/**
	 * 求长度为n的钢条最大价值 朴素递归方法
	 * r表示利润最大值
	 * @param p 钢条价格数组
	 * @param n 钢条长度
	 * @return
	 */
public int cut_Rod(int[] p, int n) {
		if (n == 0)// 长度为0,价值为0
			return 0;
		int r = -99999;//设置为最小值
		for (int i = 1; i <= n; i++)
			r = max(q, p[i] + cut_Rod(r, n - i));
		return r;
	}
	public int max(int q, int i) {
		// TODO Auto-generated method stub
		return q > i ? q : i;
	}

解决方法2——动态规划

可以看到,朴素递归方法反复求解相同的子问题,因此效率低下,动态规划方法将这些已经计算过的值保存下来,再次遇到相同的问题只需要查找相同问题的解,付出额外的空间来换取时间效率。
动态规划方法:多了一个数组r[i]用于存储长度为i的钢条的最大利润,求r[i]的过程还是使用的普通递归。

/**
	 * 求长度为n的钢条最大价值动态规划
	 * r表示利润最大值
	 * @param p 钢条价格数组
	 * @param n 钢条长度
	 * @return
	 */
	public int cut_Rod_Dynamic(int[] p, int n) {
		// new an array and initial to -9999
		int[] r = new int[n + 1];
		for (int i = 0; i < n + 1; i++)
			r[i] = -9999;
		return memoize_cut_rod_aux(p, n, r);
	}

	public int memoize_cut_rod_aux(int[] p, int n, int[] r) {
		int q;
		// 检查数组中是否有保存的值,有的话直接返回
		if (r[n] >= 0)
			return r[n];
		// 递归计算q,长度为0返回0
		if (n == 0)
			q = 0;
		else {
			q = -9999;
			for (int i = 1; i <= n; i++) {
				q = max(q, p[i] + memoize_cut_rod_aux(p, n - i, r));
			}
			r[n] = q;
		}
		return q;
	}

还可以改进这个算法,不必进行递归调用,对于已经计算过的结果我们应该加以利用:

/**
	 * 自底向上的计算长度为i的钢条的最大价格
	 * @param p
	 * @param n
	 * @return
	 */
	public int bottom_up_cut(int []p,int n)
	{
		// new an array and initial to -9999
		int[] r = new int[n + 1];
		for (int i = 0; i < n + 1; i++)
			r[i] = -9999;
		r[0]=0;//长度为0的价格为0
		for(int i =1;i<=n;i++)
		{
			int q=-9999;
			for(int j = 1;j<=i;j++)
			{
				q= max(q,p[j]+r[i-j]);//这个我们利用了r[i-j]来获取长度为i-j的钢条的最大利润
			}
			r[i]=q;
		}
		return r[n];
	}

3.扩展

3.1如果给定的钢条长度超过了表格中的最大长度,该怎么计算最大利润呢?

很简单,同样采用递归的方法,如果钢条长度大于了最大的长度,将之截成两段分别求最大利润,相加即可。所以如果对n大小没有限制的话,只需要在程序开始加一个判断就行了。

// 如果长度超过了数组的长度,分开计算,分治
		if (n > p.length - 1)
			return cut_Rod_Dynamic(p, p.length - 1) + cut_Rod_Dynamic(p, n - (p.length - 1));

但是这个中方法还是存在弊端,也是重复计算了相同的问题,有待改进。

3.2 如果要求切割方案呢?

在自底向上的切割方法中,我们可以记录当前最优解的第一段钢条的长度,声明一个额外的数组int s[],s[i]存放长度为i的钢条最优解的切割方案的第一条钢条的长度,最后打印出来即可。
总体代码如下:

public int cut_Rod_Dynamic(int[] p, int n) {
		int[] s = new int[n + 1];
		// 如果长度超过了数组的长度,分开计算,分治
		if (n > p.length - 1)
			return cut_Rod_Dynamic(p, p.length - 1) + cut_Rod_Dynamic(p, n - (p.length - 1));
		// new an array and initial to -9999
		int[] r = new int[n + 1];
		for (int i = 0; i < n + 1; i++)
			r[i] = -9999;
		int res = bottom_up_cut(p, n,s);
		//打印切割方案
		while(n>0){
			System.out.println(s[n]);
			n-=s[n];
		}
		return res;
	}
/**
	 * 自底向上的计算长度为i的钢条的最大价格,并记录切割方案
	 * @param p
	 * @param n
	 * @return
	 */
	public int bottom_up_cut(int []p,int n,int []s)
	{
		// new an array and initial to -9999
	
		int[] r = new int[n + 1];
		for (int i = 0; i < n + 1; i++) {
			r[i] = -9999;
			s[i] = -9999;
		}
		r[0]=0;//长度为0的价格为0
		for(int i =1;i<=n;i++)
		{
			int q=-9999;
			for(int j = 1;j<=i;j++)
			{
				if(q<p[j]+r[i-j]) {
					q= max(q,p[j]+r[i-j]);
					s[i]=j;//在这里记录了最优解的切割方案
				}
			}
			r[i]=q;
		}
		return r[n];
	}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值