动态规划—矩阵链乘法
矩阵链乘问题描述
给定n个矩阵构成的一个链<A1,A2,A3,.......An>,其中i=1,2,...n,矩阵A的维数为pi-1pi,对乘积 A1A2...An 以一种最小化标量乘法次数的方式进行加全部括号。
注意:在矩阵链乘问题中,实际上并没有把矩阵相乘,目的是确定一个具有最小代价的矩阵相乘顺序。找出这样一个结合顺序使得相乘的代价最低。
动态规划分析过程
1)最优加全部括号的结构
动态规划第一步是寻找一个最优的子结构。假设现在要计算AiAi+1....Aj的值,计算Ai...j过程当中肯定会存在某个k值(i<=k<j)将Ai...j分成两部分,使得Ai...j的计算量最小。分成两个子问题Ai...k和Ak+1...j,需要继续递归寻找这两个子问题的最优解。
有分析可以到最优子结构为:假设AiAi+1....Aj的一个最优加全括号把乘积在Ak和Ak+1之间分开,则Ai..k和Ak+1..j也都是最优加全括号的。
2)一个递归解
设m[i,j]为计算机矩阵Ai...j所需的标量乘法运算次数的最小值,对此计算A1..n的最小代价就是m[1,n]。现在需要来递归定义m[i,j],分两种情况进行讨论如下:
当i==j时:m[i,j] = 0,(此时只包含一个矩阵)
当i<j 时:从步骤1中需要寻找一个k(i≤k<j)值,使得m[i,j] =min{m[i,k]+m[k+1,j]+pi-1pkpj} (i≤k<j)。
3)计算最优代价
虽然给出了递归解的过程,但是在实现的时候不采用递归实现,而是借助辅助空间,使用自底向上的表格进行实现。设矩阵Ai的维数为pi-1pi,i=1,2.....n。输入序列为:p=<p0,p1,...pn>,length[p] = n+1。使用m[n][n]保存m[i,j]的代价,s[n][n]保存计算m[i,j]时取得最优代价处k的值,最后可以用s中的记录构造一个最优解。书中给出了计算过程的伪代码,摘录如下:
MAXTRIX_CHAIN_ORDER(p) 2 n = length[p]-1; 3 for i=1 to n 4 do m[i][i] = 0; 5 for t = 2 to n //t is the chain length 6 do for i=1 to n-t+1 7 j=i+t-1; 8 m[i][j] = MAXLIMIT; 9 for k=i to j-1 10 q = m[i][k] + m[k+1][i] + qi-1qkqj; 11 if q < m[i][j] 12 then m[i][j] = q; 13 s[i][j] = k; 14 return m and s;
MATRIX_CHAIN_ORDER具有循环嵌套,深度为3层,运行时间为O(n3)。如果采用递归进行实现,则需要指数级时间Ω(2n),因为中间有些重复计算。
4)构造一个最优解
第三步中已经计算出来最小代价,并保存了相关的记录信息。因此只需对s表格进行递归调用展开既可以得到一个最优解。书中给出了伪代码,摘录如下:
PRINT_OPTIMAL_PARENS(s,i,j) if i== j then print "Ai" else print "("; PRINT_OPTIMAL_PARENS(s,i,s[i][j]); PRINT_OPTIMAL_PARENS(s,s[i][j]+1,j); print")";
5)编程实现
public class Q1_Matrix_chain { public static int[] atest ={30,35,15,5,10,20,25}; public static int[] a={3, 5, 2, 1, 10}; public static int[] b={2, 7, 3, 6, 10}; public static int[] c={10, 3, 15, 12, 7, 2}; public static int[] d={7, 2, 4, 15, 20, 5}; public static void main(String[] args) { System.out.println("<3, 5, 2, 1,10>"); Matrix_Chain_Order(a); System.out.println("<2, 7, 3, 6, 10>"); Matrix_Chain_Order(b); System.out.println("<10, 3, 15, 12, 7, 2>"); Matrix_Chain_Order(c); System.out.println("<7, 2, 4, 15, 20, 5>"); Matrix_Chain_Order(d); } public static void Matrix_Chain_Order(int[] a){ int n = a.length-1; int[][] m = new int[n+1][n+1]; int[][] s = new int[n+1][n+1]; int i,j,k,t; for (i=0;i<=n;i++) m[i][i] = 0; for (i=0;i<=n;i++) s[i][i] = 0; for(t=2; t<=n; t++) //t is the chain length { for(i=1;i<=n-t+1;i++)//从第一矩阵开始计算,计算长度为t的最小代价 { j = i+t-1;//长度为t时候的最后一个元素 m[i][j] = 1000000;//初始化为最大代价 for(k=i;k<=j-1;k++)//寻找最优的k值,使得分成两部分k在i与j-1之间 { int temp = m[i][k]+m[k+1][j] + a[i-1]*a[k]*a[j]; if(temp < m[i][j]) { m[i][j] = temp; //记录下当前的最小代价 s[i][j] = k; //记录当前的括号位置,即矩阵的编号 } } } } System.out.println("一个最优解为:"); Display(s,1,n); System.out.println("\n计算的次数为:"); System.out.println(m[1][n]); } public static void Display(int[][] s,int i,int j) { if( i == j) { System.out.print('A'); System.out.print(i); } else { System.out.print('('); Display(s,i,s[i][j]); Display(s,s[i][j]+1,j); System.out.print(')'); } } }