动态规划的另类解法——钢条切割
序
作为一名算法爱好者,遇到问题总希望找到最优的解决方案,作为一名工程师,却需要在频繁变更的需求中快速给出响应。在最快和最优中,往往需要找到一种平衡。笔者在最近的工作中有所感悟,以动态规划中经典的钢条切割问题为例,在此记录一二。
问题描述
给定一段长度为 n n 英寸的钢条和一个价格表,求切割钢条方案,使得销售收益 rn r n 最大。下图给出了表格样例:
长度 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
价格 | 1 | 5 | 8 | 9 | 10 | 17 | 17 | 20 | 24 | 30 |
动态规划解法
本题是《算法导论》中动态规划这一章的经典问题,相关的解法已经很成熟了。这里直接上代码
def dp_solver(num_rods):
def memoized_cut_rod_aux(p, n, r):
if r[n-1] >= 0:
return r[n-1]
q = 0
if n > 0:
q = -1
for i in range(1, min(len(p)+1, n+1)):
q = max(q, p[i-1] + memoized_cut_rod_aux(p, n-i, r))
r[n-1] = q
return q
p = [1.0, 5.0, 8.0, 9.0, 10.0, 17.0, 17.0, 20.0, 24.0, 30.0]
r = [-1] * num_rods
res = memoized_cut_rod_aux(p, num_rods, r)
print('DP Optimal objective value = %d' % res)
整数规划解法
如果将钢条切割问题看成是带约束的整数优化问题,那么可以给出如下表达式
其中
p=[1,5,8,9,10,17,17,20,24,30] p = [ 1 , 5 , 8 , 9 , 10 , 17 , 17 , 20 , 24 , 30 ]
a=[1,2,3,4,5,6,7,8,9,10] a = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ]
使用google开源的第三方库or-tools,可以给出如下代码
from __future__ import print_function
from ortools.linear_solver import pywraplp
def ip_solver(num_rods):
list_length = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
list_price = [1.0, 5.0, 8.0, 9.0, 10.0, 17.0, 17.0, 20.0, 24.0, 30.0]
solver = pywraplp.Solver('SolveIntegerProblem',
pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)
objective = solver.Objective()
rods = [[]] * len(list_length)
for i, length in enumerate(list_length):
rods[i] = solver.IntVar(0.0, solver.infinity(), 'x_'+str(length))
objective.SetCoefficient(rods[i], list_price[i])
objective.SetMaximization()
constraint = solver.Constraint(num_rods, num_rods)
for i, length in enumerate(list_length):
constraint.SetCoefficient(rods[i], length)
"""Solve the problem and print the solution."""
result_status = solver.Solve()
print('IP Optimal objective value = %d' % solver.Objective().Value())
两种算法比较
在钢条切割这个具体问题下,简单分析这两种算法的优劣。
动态规划可以看做是一个量身定做的解法,它的效率无疑是最高的。然而缺点在于,如果增加需求,则需要调整甚至重构代码。比如说,需要考虑切割工序的成本,或者是限制某种长度的钢条个数,或者是
n
n
<script type="math/tex" id="MathJax-Element-7">n</script>太大以至于超过了递归栈的最大深度,这些都需要对代码进行调整。从工程角度来说,在需求频繁变动的情况下,没有经验的工程师会难以根据变动快速迭代,给出解决方案。
整数规划可以看做是将某种定义更为广泛的问题运用到一个特殊场景。它的效率远远不如动态规划。然而整数规划的好处在于两点。第一点,需求变更后无需调整代码结构,只需改动模型本身;第二点,作为一个成熟的轮子,不需要考虑问题规模扩大后带来的各种问题。
小结
在业务发展前期,需求变更后快速响应的要求远大于对代码效率的要求,整数规划不失为一种应急的解决方案。在业务发展相对成熟,需求稳定之后,再针对问题量身定制解决方案。