一、动态规划定义
区别于分治法,动态规划(dynamic programming)的子问题是有重叠的。常用于最优化问题(optimization problem)。
二、钢条切割问题
2.1步骤分解
(1)刻画最优解的结构特征
如何得到最大的收益->切割 or 不切割->则最大收益可以由两个子方案组成,即
最大收益 = max(不切割的收益,切割的收益)
(2)递归地定义最优解的值
不切割的收益的已知,则需定义切割的收益。由于每个长度的最优收益是固定的,所以最优切割方案也是固定的,那么问题就变成了如何定义切割的结构特征。既然每个长度的最优切割方案已知,那么只要已知一个切割长度,另一个最优方案也就已知了。即:
其中,n为带求解的长度,i为一个固定的切割长度,显然通过循环就可计算出最优值。
又,则之后的结果都可以通过递归得到。
故当前的公式为:
由于i=n时,即为pn,则公式可以改为:
(3)计算最优解的值
得到上述公式后,显然可以得到以下的递归代码:
def cut_rod(p,n):
if n==0:
return 0
q=float('-inf')
for i in range(1,n+1):
q=max(q,p[i-1]+cut_rod(p,n-i))
return q
print(cut_rod([1,5,8,9,10,17,17,20,24,30],4))
运行后可以得到结果为4。算法的复杂度为。
2.2 使用动态规划优化代码
计算n为4的步骤如下图,显然是极其冗余的。子问题的计算存在冗余,一个直接的办法是记录子问题的结果,防止重复计算。
(1)带备忘的自顶向下法
def memo_cut_rod(p,n):
r = []
for i in range(0,n+1):
r.append(float('-inf'))
return memo_cut_rod_aux(p,n,r)
def memo_cut_rod_aux(p,n,r):
if r[n] >= 0:
return r[n]
if n == 0:
q = 0
else:
q = float('-inf')
for i in range(1,n+1):
q = max(q,p[i-1]+memo_cut_rod_aux(p,n-i,r))
r[n] = q
return q
print(memo_cut_rod([1,5,8,9,10,17,17,20,24,30],4))
如代码所示,即在递归计算中,若的值已知,则不再进行递归计算,直接返回。
(2)自底向上法
核心思想是自底向上的求解,即任何子问题依赖于更小的子问题。
def memo_cut_rod(p, n):
r = []
for i in range(0, n + 1):
r.append(0)
for j in range(1, n + 1):
q = float('-inf')
for i in range(1,j+1):
q = max(q, p[i-1] + r[j - i])
r[j] = q
return q
print(memo_cut_rod([1, 5, 8, 9, 10, 17, 17, 20, 24, 30], 4))
显然,每个的值都依赖于之前的值,如此便可以得到最后的代码,算法的复杂度为
。