动态规划之矩阵链乘法(第15章)
实验室催促毕业论文进展,所以今天下午我得收拾一下,准备一下毕业论文需要弄的东西。 唉, 感觉自己就是太笨了。 ——题外话。
1.动态规划的原理
[1] 什么情况下使用动态规划?
适合应用动态规方法的求解最优化问题应该具备两个要素:最优子结构和子问题重叠。使用动态规划方法求解最优化问题的第一步就是刻画最优解的结构。如果一个问题的最优解包含子问题的最优解,我们就成此类问题具有-最优解结构-性质。第二个条件子问题重叠,子问题的空间足够的小,即问题的递归算法会反复的求解相同的子问题,而不是一味的生成新的子问题。如果递归算法反复求解相同的子问题,我们就称最优化问题具有-重叠子问题-的性质。动态规划算法通常利用重叠子问题的性质:对每个子问题求解一次,将解存入一个表中,当再次需要这个子问题的时候直接查表,每次查表的代价为常量时间。
[2] 重构最优解和备忘机制
从实际的角度考虑我们通常将每个子问题所做的选择存在一个表中,这样就不需要根据代价值来重构这些信息。
备忘机制:在递归的算法中加入备忘机制,可以让自顶向下的递归算法达到和自底向上的动态规划算法相似的效率。
2.矩阵链乘法概述
Description
给定n个矩阵{A1,A2,…,An},其中Ai与Ai+1是可乘的,i=1,2 ,…,n-1。如何确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少。
Input
有N个矩阵连乘,用一行有n+1个数数组表示,表示是n个矩阵的行及第n个矩阵的列,它们之间用空格隔开.
Output
你的输出应该有C行,即每组测试数据的输出占一行,它是计算出的矩阵最少连乘积次数,输出最优全括号结构
3.矩阵链乘法算法分析与设计
矩阵链乘法问题动态规划分析:
给定由n个矩阵构成的序列[A1,A2,…,An],对乘积A1A2…An,找到最小化乘法次数的加括号方法。
1)寻找最优子结构
此问题最难的地方在于找到最优子结构。对乘积A1A2…An的任意加括号方法都会将序列在某个地方分成两部分,也就是最后一次乘法计算的地方,我们将这个位置记为k,也就是说首先计算A1…Ak和Ak+1…An,然后再将这两部分的结果相乘。
最优子结构如下:假设A1A2…An的一个最优加括号把乘积在Ak和Ak+1间分开,则前缀子链A1…Ak的加括号方式必定为A1…Ak的一个最优加括号,后缀子链同理。一开始并不知道k的确切位置,需要遍历所有位置以保证找到合适的k来分割乘积。
2)构造递归解
设m[i,j]为矩阵链Ai…Aj的最优解的代价,则
┌ 0 如果i = j
m[i,j] = │
└ min(i≤k<j) {m[i,k] + m[k+1,j] + Ai.row*Ak.col*Aj.col} 如果i < j
3)构建辅助表,解决重叠子问题
从第二步的递归式可以发现解的过程中会有很多重叠子问题,可以用一个n*n维的辅助表 m[n, n]来保存子问题的解,表中每个元素包含2个信息,分别是最优乘积代价及其分割位置k 。辅助表 m[n, n]可以由2种方法构造,一种是自底向上填表构建,该方法要求按照递增的方式逐步填写子问题的解,也就是先计算长度为2的所有矩阵链的解,然后计算长度3的矩阵链,直到长度n;另一种是自顶向下填表的备忘录法,该方法将表的每个元素初始化为某特殊值(本问题中可以将最优乘积代价设置为一极大值),以表示待计算,在递归的过程中逐个填入遇到的子问题的解。
备忘录法会比自底向上法慢一个常数因子,因为前者有递归调用的代价,维护表格的开销也稍大。不过如果动态规划中有些的子问题是不需要的话,自顶向下可能会少计算一些子问题。
4)构建最优解
虽然m[n, n]可以记录矩阵链乘积所需的最小的标量乘法的运算次数,但是它并没有直接支出如何进行这种最优代价的矩阵链乘法的计算完全加括号的方式。辅助表s[1…n-1,2….n]记录了构造最优解所需的信息。每个表项s[i,j]记录一个k值,指出
核心算法设计:
4.矩阵链乘法Java实现
矩阵链乘法核心类
//矩阵链乘法核心类
package lbz.ch15.dp.ins2;
/**
* @author LbZhang
* @version 创建时间:2016年3月9日 上午10:29:42
* @description 矩阵链乘法核心类
*/
public class MatrixChain {
public static Pair matrixChainOrder(int[] p) {
int n = p.length - 1;
int i, j, L, k, q;
int[][] m = new int[n + 1][n + 1];
int[][] 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++) { // /这个地方n也算是一个 eg 6-2+1=5 5,6
// 就是一组
j = i + L - 1; // /这个地方我们可以分析得 j-L+1=i 类似于上面一行的说明解释
m[i][j] = Integer.MAX_VALUE; // /预先设置m[i][j]等于无穷大
for (k = i; k <= j - 1; k++) {
q = m[i][k] + m[k + 1][j] + p[i - 1] * p[k] * p[j]; // /用于暂时存储i-j子矩阵链中的求解的值
// ////由于全部加括号 那么相互之间构成括号结合的两项肯定是相互靠近在一起的两项
if (q < m[i][j]) {
m[i][j] = q;
s[i][j] = k; // /分裂位置就是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(")");
}
}
}
矩阵链乘法辅助类
package lbz.ch15.dp.ins2;
/**
* @author LbZhang
* @version 创建时间:2016年3月9日 上午10:36:20
* @description 矩阵链乘法辅助类
*/
public class Pair {
public static int[][] mm ;
public static int[][] ss;
public static Pair make(int[][] m, int[][] s) {
mm = m;
ss = s;
return null;
}
/**
* 调用方法
* @param args
*/
public static void main(String[] args) {
int[] p = {30,35,15,5,10,20,25};
MatrixChain.matrixChainOrder(p);
MatrixChain.printOptimalParens(Pair.ss, 1, 6);
}
}