算法复习——动态规划篇之钢条切割问题
以下内容主要参考中国大学MOOC《算法设计与分析》,墙裂推荐希望入门算法的童鞋学习!
1. 问题背景
钢铁切割:
现有一段长度为10的钢条,可以零成本将其切割为多段长度更小钢条。
比如有以下几种切割方案:
问题是怎么合理切割,使总收益最大?
2. 问题定义
钢条切割问题(Rod Cutting Problem)
输入:
- 钢条长度 n n n
- 价格表 p [ l ] ( 1 ≤ l ≤ n ) p[l](1 \leq l \leq n) p[l](1≤l≤n):表示长度为 l l l的钢条价格
输出:
-
求解一组切割方案 T = < c 1 , c 2 , … , c m > T=<c_1, c_2, \dots, c_m> T=<c1,c2,…,cm>,令 m a x ∑ l = 1 m p c l max\sum_{l=1}^mp_{c_l} max∑l=1mpcl
s . t . ∑ l = 1 m c l = n s.t. \sum_{l=1}^mc_l=n s.t.∑l=1mcl=n
3. 问题简化
比如有一根长度为10的钢条。
- 假设至多切割1次
- 枚举所有可能的切割位置
- 不切: p [ 10 ] p[10] p[10]
- 切割: p [ i ] + p [ 10 − i ] p[i]+p[10-i] p[i]+p[10−i]
- 最大收益 m a x 1 ≤ i ≤ 9 { p [ i ] + p [ 10 − i ] , p [ 10 ] } max_{1 \leq i \leq 9}\{p[i]+p[10-i], p[10]\} max1≤i≤9{p[i]+p[10−i],p[10]}
- 枚举所有可能的切割位置
- 假设至多切割2次
- 先将钢条切割一段
- 在剩余钢条中继续切割
- 原始问题不限制切割次数
因此,可能存在最优子结构和重叠子问题。
4. 动态规划
4.1 问题结构分析
- 给出问题表示:
- C [ j ] C[j] C[j]:切割长度为 j j j的钢条可得最大总收益
- 明确原始问题:
- C [ n ] C[n] C[n]:切割长度为 n n n的钢条可得最大总收益
4.2 递推关系建立
4.2.1 分析最优(子)结构
C [ j ] = m a x 1 ≤ i ≤ j − 1 { p [ i ] + C [ j − i ] , p [ j ] } C[j]=max_{1 \leq i \leq j-1}\{p[i]+C[j-i], p[j]\} C[j]=max1≤i≤j−1{p[i]+C[j−i],p[j]}
4.2.2 构造递推公式
对于每个钢条长度 j j j, C [ j ] = m a x 1 ≤ i ≤ j − 1 { p [ i ] + C [ j − i ] , p [ j ] } C[j]=max_{1 \leq i \leq j-1}\{p[i]+C[j-i], p[j]\} C[j]=max1≤i≤j−1{p[i]+C[j−i],p[j]}
这个问题和之前的动态规划问题最明显的区别在于它不再只依赖于常数个子问题,而是需要依赖于 j j j种值的枚举情况,在所有情况中找最大者。
4.3 自底向上计算
4.3.1 确定计算顺序
- 初始化: C [ 0 ] = 0 C[0]=0 C[0]=0(切割长度为0的钢条,总收益为0)
- 递推公式: C [ j ] = m a x 1 ≤ i ≤ j − 1 { p [ i ] + C [ j − i ] , p [ j ] } C[j]=max_{1 \leq i \leq j-1}\{p[i]+C[j-i], p[j]\} C[j]=max1≤i≤j−1{p[i]+C[j−i],p[j]}
4.3.2 依次求解问题
4.4 最优方案追踪
4.4.1 记录决策过程
-
构造决策数组 r e c [ 1.. n ] rec[1..n] rec[1..n]
-
r e c [ j ] rec[j] rec[j]记录长度为 j j j钢条的最优切割方案,如果不切, r e c [ j ] = j rec[j]=j rec[j]=j;for则, r e c [ j ] = k rec[j]=k rec[j]=k
4.4.2 输出最优方案
根据追踪数组,递归输出方案
5. 伪代码
RodCutting(p, n)
输入:钢条价格表 p [ 1.. n ] p[1..n] p[1..n],钢条长度 n n n
输出:最大收益 C [ n ] C[n] C[n],钢条切割方案
// 初始化
新建一维数组C[0..n],rec[0..n]
C[0] ← 0
// 动态规划
for j ← 1 to n do
q ← p[j]
rec[j] ← j
for i ← 1 to j - 1 do
if q < p[i] + C[j-i] then
q ← p[i] + C[j-i]
rec[j] ← i
end
end
C[j] ← q
end
// 输出最优方案
while n > 0 do
print rec[n]
n ← n - rec[n]
end
该算法的时间复杂度是 O ( n 2 ) O(n^2) O(n2)。