动态规划之钢条切割

本文介绍了如何使用动态规划解决钢条切割问题,以最大化收益。动态规划方法包括四个步骤:刻画最优解结构,递归定义最优解的值,计算最优解的值,以及构造最优解。文中给出了具体的递归定义和自底向上的动态规划解决方案,并对比了两者的时间复杂度。还展示了不同长度钢条的最大收益及其切割方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

动态规划简介(算法导论)

动态规划方法通常用来求解最优化问题。这类问题可以有很多可行解,每个解都有一个值,我们希望寻找具有最优值(最小值或最大值)的解。我们称这样的解为问题的一个最优解,但是不是唯一的最优解,因为可能有多个解都能达到最优值。

我们通常按如下4个步骤来设计一个动态规划算法:

  1. 刻画一个最优解的结构特征。
  2. 递归地定义最优解的值。
  3. 计算最优解的值,通常采用自底向上的方法。
  4. 利用计算出的信息构造一个最优解。

    钢条切割

    问题描述

    给定一段长度为 nn 英寸的钢条和一个价格表 pi(i=1,2,…,n)pi(i=1,2,…,n), 求切割钢条方案,使得销售收益 rnrn 最大。
长度 ii12345678910
价格 pipi1589101717202430

表1.1 钢条价格样例。每段长度为 ii 英寸的钢条为公司带来 pipi 美元的收益

动态规划求解

长度为 nn 英寸的钢条共有 2n−12n−1 种不同的切割方案。在距离钢条左端 i(i=1,2,…,n−1)i(i=1,2,…,n−1) 英寸处,我们可以选择切割或者不切割。为了方便描述,我们用加法符号表示切割方案,比如7=2+2+3表示将长度为7英寸的钢条切割为三段(长度为2、2、3)。
如果一个最优解将钢条切割 kk 段(1<=k<=n1<=k<=n),那么最有切割方案,可以用如下表示方式

 

n=i1+i2+…+ikn=i1+i2+…+ik

,
将钢条切割为长度为 i1,i2,…,iki1,i2,…,ik 的小段,得到最大利益表示如下

rn=pi1+pi2+…+pikrn=pi1+pi2+…+pik

最优解结构

为了求解规模为 nn 的原问题,我们可以将其分解为规模更小的子问题求解。如果问题的最优解由相关子问题的最优解组合得到,我们则称此问题满足最优子结构。在钢条切割问题上,我们完成钢条首次切割后,可以将两段钢条看成两个独立的钢条切割实例。通过组合两个子问题的最优解,并在所有可能中取收益最大的组合方案得到最优解。

递归定义

将钢条从左边切割长度为 ii 的一段,右边剩下长度为 n−1n−1 ,假定左边钢条不再切割,只需对右边钢条继续切割,使用递归的思想求解。可得到以下递归版本定义

 

rn=max(pi+rn−i),1≤i≤n.rn=max(pi+rn−i),1≤i≤n.

 

1
2
3
4
5
6
7
8
9
10
11
12
public static long getCutRod(long[] p, int n) {
		if (n == 0)
			return 0;
		long q = Long.MIN_VALUE;
		for (int i = 1; i <= n; i++) {
			if(i==11){
				System.out.println(i);
			}
			q =  Math.max(q, p[i] + getCutRod(p, n - i));
		}
		return q;
	}

 

其中数组p表示价格数组,数组大小为n+1,初始化如下
if(n<=10),数组大小定义为11,初始化如下
p[0] = 0;
p[1] = 1;
p[2] = 5;
p[3] = 8;
p[4] = 9;
p[5] = 10;
p[6] = 17;
p[7] = 17;
p[8] = 20;
p[9] = 24;
p[10] = 30;
其他情况,数组大小根据输入的n值大小动态确定,前11个值初始化为上面所提,其他为默认值0
但是该版本的运行时间随着n的增加呈指数增长,后面会给出对比曲线。

计算最优值

我们采用自底向上的动态规划的方法得到更有效的算法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static long getCutRodDP(long[] p, int n) {
		long[] dp = new long[n + 1];
		int[] length = new int[n + 1];
		dp[0] = 0;
		for (int i = 1; i <= n; i++) {
			long q = Long.MIN_VALUE;
			for (int j = 1; j <= i; j++) {
				long temp = q;
				q = j < p.length ? Math.max(q, p[j] + dp[i - j]) : Math.max(q, dp[j] + dp[i - j]);
				if (temp < q) {
					length[i] = j;
				}
			}
			dp[i] = q;
		}
		 return dp[n];
	}

 

p表示价格表,如本文中应该为p的数组大小应该为11。
动态规划版本与递归版本时间消耗对比如下,可以明显看到递归版本的时间呈指数增长
首页
当钢条长度增长到35时,鄙人渣渣电脑就跑不动了,以下表示动态规划算法版本的时间消耗表

长度2004006008001000150020004000600010000
耗时(ms)1346711132954133

构造最优解

前面我们进给出最优解的值,并没给出具体切割方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static String getCutRodDP(long[] p, int n) {
		long[] dp = new long[n + 1];
		int[] length = new int[n + 1];
		dp[0] = 0;
		for (int i = 1; i <= n; i++) {
			long q = Long.MIN_VALUE;
			for (int j = 1; j <= i; j++) {
				long temp = q;
				q = j < p.length ? Math.max(q, p[j] + dp[i - j]) : Math.max(q, dp[j] + dp[i - j]);
				if (temp < q) {
					length[i] = j;
				}
			}
			dp[i] = q;
		}
		//构造最优解
		int temp = n;
		StringBuffer str = new StringBuffer("r" + n + "=" + dp[n] + "," + "切割方案" + n + "=");
		while (temp > 0) {
			str.append(length[temp]);
			str.append("+");
			temp = temp - length[temp];
		}
		str.delete(str.length() - 1, str.length());
		return str.toString();
	}

 

测试
r11=31,切割方案11=1+10,
r15=43,切割方案15=2+3+10,
r33=98,切割方案33=3+10+10+10

完整代码以及相应示例图见 https://github.com/lemon2013/algorithm/blob/master/src/com/pty/graph/Cutrod.java

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值