一、动态规划概述
第四部分将会学习针对一类具有特殊性质的组合优化问题来讨论一种解决问题的算法设计技巧——“动态规划”。所谓的组合优化问题,指的是问题有多个可行解,每一个可行解对应一个目标值,目的是要在可行解中求得目标值最优者(最大或最小)。适合于用动态规划方法解决的组合优化问题有如下两个特征。
1、最优子结构
所谓的最优子结构特性指的是问题的最优解包含的子问题的解相对于子问题而言也是最优的。
2、子问题重叠
与适用分治法的问题不同,问题的一个递归算法在每个递归步骤产生分支子问题时并不总是新的,而是对部分子问题解了又解。当一个递归算法一次又一次的访问同一个子问题的时候,我们说该最优问题具有重叠子问题的特性。
3、动态规划
针对具有上述两个特征的优化问题,动态规划算法通常需要做如下三步的工作:
- 利用最优子结构定义一个关于解的目标值的递归方程。鉴于子问题的重叠性,如果自顶向下地用递归技术解每一个遇到的子问题,则可能陷入一个时间黑洞。
- 因此,动态规划以自底向上地对每个新产生的子问题仅解一次且将其解保存在一个表格中,需要时可以在表中查找,且能在常数时间内完成查找。
- 根据计算出的最优解的值构造对应的最优解。
二、矩阵链乘法
1、算法描述与分析
①矩阵相乘问题
计算两个矩阵的乘法
MATRIX-MULTIPLY(A,B)
if columns[A] != rows[B]
then error "矩阵乘法不相容"
else for i<-1 to rows[A]
do for j<-1 to columns[B]
do C[i,j]<-0
for k<-1 to columns[A]
do C[i,j]<-C[i,j]+(A[i,k]*B[k,j]
return C
②矩阵链乘法算法描述与分析
③最优子结构
④子问题与转移方程
其中Pi-1PkPj的代价可以通过矩阵相乘计算而得。
2、算法的伪代码描述
计算矩阵链积所需的最少标量乘法数,还计算了用于构造最优解的表格s[1....n,1....n]。每一项s[i,j]记录了AiAi+1....Aj的最佳加括号的在Ak和Ak+1之间的分裂值k。
MATRIX-CHAIN-ORDER(p)
n <- length[p] - 1
for i<-1 to n //初始化
do m[i,j]<-0
for l<-2 to n //l是矩阵链的长度
do for i<-1 to n-l+1
do j<-i+l-1
m[i,j]<-Integer.MAX_VALUE //处理初始值
for k<-i to j-1
do q<-m[i,k] + m[k+1,j] + Pi-1PkPj
if q<m[i,j]
then m[i,j]<-q
s[i,j]<-k
return m and s
我们知道最终矩阵积A1...n的最佳计算是A1...s[1,n]乘以As[1,n]+1...n,打印出矩阵链的最佳完全加括号:
PRINT-OPTIMAL-PARENS(s,i,j)
if i=j
then print "A"i
else print "("
PRINT-OPTIMAL-PARENS(s,i,s[i,j])
PRINT-OPTIMAL-PARENS(s,s[i,j]+1,j)
print ")"
3、Java语言实现
①封装两个任意对象的类Pair
public class Pair {
public Object first;
public Object second;
public Pair(){
first = new Object();
second = new Object();
}
public static Pair make(Object a ,Object b){
Pair p = new Pair();
p.first = a;
p.second = b;
return p;
}
}
②实现MatrixChainOrder和printOptimalParens伪代码
public class MatrixChain {
public static Pair matrixChainOrder(int []p){
int n=p.length-1,i,l,j,k,q;
int[][] m = new int[n+1][n+1],s = new int[n+1][n+1];//多开辟一行一列为了使下标对应
for (i=0;i<n;i++)
m[i][i] = 0;
for (l=2;l<=n;l++){//l为子矩阵链的长度
for (i=1;i<=n-l+1;i++){//二维数组的循环赋值
j=i+l-1;//j-i+1等于l
m[i][j] = Integer.MAX_VALUE;
for (k=i;k<=j-1;k++){//计算顺序很重要
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 Pair.make(m,s);
}
public static void printOptimalParens(int[][] s,int i,int j){
if (i==j)
System.out.printf("A%d",i);
else {
System.out.print("(");
printOptimalParens(s,i,s[i][j]);
printOptimalParens(s,s[i][j]+1,j);
System.out.print(")");
}
}
}
③测试类
public class Test {
public static void main(String[] args) {
int p[] = {30,35,15,5,10,20,25};
int[][] m,s;
Pair r = MatrixChain.matrixChainOrder(p);
m = (int[][]) r.first;
s = (int[][]) r.second;
MatrixChain.printOptimalParens(s,1,6);
System.out.println();
System.out.println(m[1][6]);;
}
}