第15章 动态规划
15.1 钢条切割
1、递归实现:
CUT-ROD(p,n)
if n == 0
return 0
q = -\inf
for i = 1 to n
q = max(q,p[i]+CUT-ROD(p,n-i))
return q
分析CUT-ROD的运行时间,令T(n)表示第2个参数值为n时CUT-ROD的调用次数。
T(n)=1+∑j=0n−1T(j)=2n
2、使用动态规划
- 带备忘的自顶向上法
MEMOIZED-CUT-ROD(p,n)
let r[0..n] be a new array
for i = 0 to n
r[i] = -\inf
return MEMOIZED-CUT-ROD-AUX(p,n,r)
MEMOIZED-CUT-ROD-AUX(p,n,r)
if r[n] >= 0
return r[n]
if n == 0
q = 0
else q = -\inf
for i = 1 to n
q = max(q,p[i]+MEMOIZED-CUT-ROD-AUX(p,n-i,r))
r[n] = q
return q
- 自底向上法
BOTTOM-CUT-ROD(p,n)
let r[0..n] be a new array
r[0] = 0
for j = 1 to n
q = -\inf
for i = 1 to j
q = max(q,p[i]+r[j-i])
r[j] = q
return r[n]
两种方法具有相同的渐进运行时间,但自底向上法没有频繁的递归函数调用的开销,所以较自顶向下法有更小的系数。
3、重构解
EXTENDED-BOTTOM-CUT-ROD(p,n)
let r[0..n] and s[0..n] be a new array
r[0] = 0
for j = 1 to n
q = -\inf
for i = 1 to j
if q < p[i]+r[j-i]
q = p[i]+r[j-i]
s[j] = i
r[j] = q
return r[n]
PRINT-CUT-ROD-SOLUTION(p,n)
(r,s) = EXTENDED-BOTTOM-CUT-ROD(p,n)
while n > 0
print s[n]
n = n - s[n]
15.2 矩阵链乘法
m[i,j]表示 AiAi+1...Aj 矩阵链的最优解
MATRIX-CHAIN-ORDER(p)
n = p.length - 1
let m[1..n,1..n] and s[1..n-1,2..n] be a new array
for i = 1 to n
m[i,i] = 0
for l = 2 to n //l is the chain length
for i = 1 to n-l+1
j = i + l -1
m[i,j] = \inf
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,s
PRINT-OPTIMAL-PARENS(s,i,j)
if i == j
print "A"
else print "("
PRINT-OPTIMAL-PARENS(s,i,s[i,j])
PRINT-OPTIMAL-PARENS(s,s[i,j]+1,j)
print ")"
15.3 动态规划原理
最优子结构
在发掘最优子结构性质的过程中,实际上遵循了如下的通用模式:
1. 证明问题最优解的第一个组成部分是做出一个选择
2. 对于一个给定问题,在其可能的第一步选择中,假定已经知道哪种选择才会得到最优解
3. 给定可获得最优解的选择后,确定这次选择会产生哪些子问题,以及如何最好地刻画子问题空间
4. 利用“剪切-粘贴”技术证明作为构成原问题最优解的组成部分,每个子问题的解就是它本身的最优解
重叠子问题
适合用动态规划方法求解的最优化问题,其子问题空间必须足够“小”。如果递归算法反复求解相同的子问题,我们就称最优化问题具有重叠子问题。
重构最优解
维护记录每个子问题最优位置的表,重构时只需O(1)时间。
备忘
在低效的递归算法加入备忘机制可称为自顶向下动态规划法。通常情况下,自底向上动态规划算法没有递归调用的开销,会比自顶向下备忘算法快。但对于某些问题,它的子问题空间中的某些子问题完全不必求解,备忘方法就会体现出优势了,因为它只会求解那些绝对必要的子问题。
15.4 最长公共子序列
public static int lcs(String str1, String str2) {
int len1 = str1.length();
int len2 = str2.length();
int c[][] = new int[len1+1][len2+1];
for (int i = 1; i <= len1; i++) {
for( int j = 1; j <= len2; j++) {
if (str1.charAt(i-1) == str2.charAt(j-1))
c[i][j] = c[i-1][j-1] + 1;
else
c[i][j] = max(c[i - 1][j], c[i][j - 1]);
}
}
return c[len1][len2];
}
15.5 最优二叉搜索树
OPTIMAL-BST(p,q,n)
let e[1..n+1,0..n],w[1..n+1,0..n], and root[1..n,1..n] be new tables
for i = 1 to n+1
e[i,i-1] = q[i-1]
w[i,i-1] = q[i-1]
for l = 1 to n
for i = 1 to n-l+1
j = i+l-1
e[i,j] = \inf
w[i,j] = w[i,j-1]+p[j]+q[j]
for r = i to j-1
t = e[i,r-1]+e[r+1,j]+w[i,j]
if t<e[i,j]
e[i,j]=t
root[i,j]=r
return e,root