《算法设计与分析》学习笔记之一:动态规划
《算法设计与分析》学习笔记之一:动态规划
动态规划(dynamic programming)
用于求解最优化问题(求目标函数 f(x) 在约束条件 x∈D 下的最小值或最大值的问题),即找到可行解中的一个最优解(最小值或最大值,an optional solution)。
与分治法的区别:分治应用于互不相交的子问题递归求解,动态规划应用于子问题重叠的情况;子问题重叠时,分治会反复求解重叠的子问题,但动态规划对每个子子问题只求解一次。
钢条切割问题
给定 n 英寸的钢条和价格表 P ,求使销售收益 rn 最大的切割方案。
现在设 rn 是最优切割的收益,考虑存在一次切割在位置 i ,则有:rn = ri + rn-i 。即有 rn = max 1≤j≤n { rj + rn-j , pn } 。
这体现了最优子结构性:问题的最优解由相关子问题的最优解组合而成,而这些子问题可以独立求解。
问题还能够简化:我们切成两段后只切割右侧 n - i 的部分,不再切割左侧的 i 。即将 “可能的一次切割的位置” 简化为 “最左侧切割的位置”,则有 rn = max 1≤j≤n { pj + rn-j } 。
如果我们用类似分治的简单自顶向下算法:
// Algorithm 1
CUT-ROD(p,n)
if n == 0
q = 0
else q = -∞
for i = 1 to n
q = max(q, p[i]+CUT-ROD(p,n-i))
return q
则递归调用树如图15-3。若 T(n) 表示函数参数为 n 时的调用次数,等于递归调用树中根为 n 的子树中的结点总数,即
该递归调用树共有 2n 个结点(即 T(n) = 2n ),其中 2n-1 个叶子结点。(易证)
接下来使用两种动态规划的实现方法,它们是等价的,且均具有相同的渐进运行时间 Θ(n2) 。
带备忘的自顶向下法(top-down with memorization)
仍按自然递归编写过程,但保存每个子问题的解,若保存过则计算式直接返回此解(“带备忘的”,memorized)。
// Algorithm 2
MEMORIZED-CUT-ROD(p,n)
let r[0...n] be a new array
for i = 0 to n
r[i] = -∞
return MEMORIZED-CUT-ROD-AUX(p,n,r) // 此步建立了一个新数组,单独成为函数的原因是递归调用时不用再建立此数组
MEMORIZED-CUT-ROD-AUX(p,n,r)
if r[n] ≥ 0
return r[n] // 在 Algorithm 1 前加一个直接输出的判断
if n == 0
q = 0
else q = -∞
for i = 1 to n
q = max(q, p[i]+MEMORIZED-CUT-ROD-AUX(p,n-i,r))
r[n] = q // 在 Algorithm 1 后加上保存已经求出的解的过程
return q
自底向上法(bottom-up method)
按从小到大的顺序顺次求解。
// Algorithm 3
BOTTOM-UP-CUT-ROD(p,n)
let r[0...n] be a new array
r[0] = 0 // 该方法在代码形式上是非递归的自底向上递推方法(函数并未调用自己)
// 最后返回 r[n] 而非 q,所以不能像上面在 if-else 语句中使 q=0 赋值并返回
// 而必须在前面直接指定 r[0] = 0 作为递推的初始条件
for j = 1 to n
q = -∞
for i = 1 to j
q = max(q,p[i]+r[j-i])
r[j]=q
return r[n]
子问题图
自顶向下递归调用树的简化版,是有向图,边 (x, y) 表示 x 的求解依赖 y 。自底向上处理顺序是子问题图的逆序。
解重构
不仅要求出最优收益,还要求出切割方案。
// Algorithm 4
EXTENDED-BOTTOM-UP-CUT-ROD(p,n)
let r[0...n] and s[0...n] be new arrays
r[0] = 0
for j = 1 to n
q = -∞
for i = 1 to j
if q < p[i] + r[j-1]
q = p[i] + r[j-i] // 不能直接写max,因为同时还要使s[j]=i
s[j] = i // 记录各个长度钢条最优的“第一刀”
r[j] = q
return r[n]
PRINT-CUT-ROD-SOLUTION(p,n)
(r,s) = EXTENDED-BOTTOM-UP-CUT-ROD(p,n)
while n > 0
print s[n]
n = n - s[n] // 递推打印最优切割方案
矩阵链乘法
给定 n 个矩阵的链,矩阵 Ai 的维数为 pi-1 × pi 。求一个完全“括号化方案”,使得计算该矩阵链乘积所需的标量乘法次数最小。
前置知识
矩阵乘法:C = Ap×r × Br×q = (cij)p×q,其中 cij = Σ1≤k≤r (aikbkj) ,i = 1, 2, ..., p, j = 1, 2, ..., q,共需 pqr 次标量乘法运算。
矩阵链乘法:
满足结合律。
// Algorithm 1
MATRIX-MULTIPLY(A,B)
if A.columns != B.rows
error
else let C be a new A.rows × B.columns matrix
for i = 1 to A.rows
for j = 1 to B.columns
c(ij) = 0
for k = 1 to A.columns
c(ij) = c(ij) + a(ik)*b(kj)
return C
求解
令 P(n) 表示所有的括号化方案数量。有
且易证 P(n) = Ω(2n) 。
- 最优子结构性的证明(反证法)。
- 递归求解方案:(记 m[i,j] 为矩阵链乘法 Ai,j 标量乘法运算次数最优解,s[i,j] 为使 m[i,j] 最优时的k,Ai,j 的最优括号化方案的分割点在 Ak 与 Ak+1 之间)
自底向上表格法
// Algorithm 2
MATRIX-CHAIN-ORDER(p)
n = p,length - 1
let m[1..n,1..n] and s[1..n,1..n] be new tables // “表格”即二维数组
for i = 1 to n
m[i,i] = 0
for l = 2 to n // 选取的矩阵链长度
for i = 1 to n-l+1 // 该矩阵链长度的初始下标
j = i+l-1 // 由该矩阵链长度的初始下标计算最后一位的下标
m[i,j] = ∞
for k = i to j-1
q = m[i,k] + m[k+1,j] + p(i-1)p(k)p(j)
if q < m[i,j]
m[i,j] = q
s[i,j] = k
return m and s
可作出对应的 m 表和 s 表(如图15-5)。
-
时间复杂度:Ω(n3),空间复杂度:Θ(n2)。
-
构造最优解。
// Algorithm 3
PRINT-OPTIMAL-PARENS(s,i,j)
if i==j
print "A"(i)
else print "("
PRINT-OPTIMAL-PARENS(s,i,s[i,j])
PRINT-OPTIMAL-PARENS(s,s[i,j]+1,j)
print ")"
动态规划的一般方法
决策,决策变量,决策序列,多阶段决策过程。
无后效性:对任意阶段 i ,阶段 i 以后的行为仅依赖于 i 阶段的状态,而与 i 阶段之前是如何达到这种状态的无关。
状态转移方程:xi+1 = Ti ( x(i) , u(i) )。
多阶段决策过程的最优化问题即求对应的最优决策序列,方法:枚举法、动态规划。
最优化原理(Principle of Optimality):过程的最优决策序列具有以下性质:无论初态和初始决策如何,其余决策必须相对于初始决策产生的状态构成一个最优决策序列。
子问题无关性。e.g. 最短路径问题具有最优子结构性。最长简单路径问题不具有最优子结构性。最优子结构性的证明:“剪切-粘贴”(cut-and-paste)技术,即反证法。
若问题的决策序列由 n 次决策构成,而每次决策有 p 种选择,则可能的决策序列将有 pn 个。利用动态规划策略的求解过程中仅保存了所有子问题的最优解,舍去了不能导致最优解的次优决策序列,可能有多项式的计算复杂度。
浙公网安备 33010602011771号