本文介绍应用动态规划算法求解 cutting stock 问题的案例。
类似地,动态规划算法也可以用来求解背包问题。
其他动态规划的应用实例:
动态规划的应用(一):最短路问题
动态规划的应用(三):字符串相关问题
动态规划的应用(四):LeetCode 1900. 最佳运动员的比拼回合
动态规划的应用(五):LeetCode 413, 446. 等差数列划分
动态规划的应用(六):矩阵相关问题
例
钢条的价格如下表所示,求最大利润。(选自《算法导论》)
长度 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
价格 | 1 | 5 | 8 | 9 | 10 | 17 | 17 | 20 | 24 | 30 |
递归版本
动态规划的递归版本代码如下:
from typing import List
def cut_run(p: List[int]) -> int:
r = [0 for _ in range(len(p))] # "备忘录",存储各长度下的最优解
return cut(p=p, n=len(p), r=r)
def cut(p: List[int], n: int, r: List[int]) -> int:
if r[n - 1] > 0: # 查找备忘录,避免重复遍历解空间
return r[n - 1]
if not n:
return 0
q = 0
for i in range(1, n + 1):
q = max(q, p[i - 1] + cut(p, n - i, r)) # 递归方程
r[n - 1] = q # 更新备忘录
print("备忘录元素 {0} 更新为 {1},列表 {2}".format(n, q, r))
return q
p_ = [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]
print()
q_ = cut_run(p=p_)
print()
print("最大利润: {}".format(q_), '\n')
运行结果:
备忘录元素 1 更新为 1,列表 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
备忘录元素 2 更新为 5,列表 [1, 5, 0, 0, 0, 0, 0, 0, 0, 0]
备忘录元素 3 更新为 8,列表 [1, 5, 8, 0, 0, 0, 0, 0, 0, 0]
备忘录元素 4 更新为 10,列表 [1, 5, 8, 10, 0, 0, 0, 0, 0, 0]
备忘录元素 5 更新为 13,列表 [1, 5, 8, 10, 13, 0, 0, 0, 0, 0]
备忘录元素 6 更新为 17,列表 [1, 5, 8, 10, 13, 17, 0, 0, 0, 0]
备忘录元素 7 更新为 18,列表 [1, 5, 8, 10, 13, 17, 18, 0, 0, 0]
备忘录元素 8 更新为 22,列表 [1, 5, 8, 10, 13, 17, 18, 22, 0, 0]
备忘录元素 9 更新为 25,列表 [1, 5, 8, 10, 13, 17, 18, 22, 25, 0]
备忘录元素 10 更新为 30,列表 [1, 5, 8, 10, 13, 17, 18, 22, 25, 30]
最大利润: 30
非递归版本
动态规划的非递归版本代码如下:
from typing import List
def cut(p: List[int]) -> int:
r = [0 for _ in range(len(p) + 1)]
for i in range(1, len(p) + 1): # 外循环:求备忘录列表 r
q = 0
for j in range(1, i + 1): # 内循环:求备忘录列表 r 各元素的值
q = max(q, p[j - 1] + r[i - j])
r[i] = q
print("备忘录元素 {0} 更新为 {1},列表 {2}".format(i + 1, q, r))
return r[len(p)]
p_ = [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]
print()
q_ = cut(p=p_)
print()
print("最大利润: {}".format(q_), '\n')
显然,运行结果与递归版本相同。