《问题的引出》(《算法设计与分析》,王晓东编著,清华大学出版社2008年1月第2版。)
看下面一个例子,计算三个矩阵连乘{A1,A2,A3};维数分别为10*100 , 100*5 , 5*50
按此顺序计算需要的次数((A1*A2)*A3):10X100X5+10X5X50=7500次
按此顺序计算需要的次数(A1*(A2*A3)):10X5X50+10X100X50=75000次
所以问题是:如何确定运算顺序,可以使计算量达到最小化。
枚举显然不可,如果枚举的话,相当于一个“完全加括号问题”,次数为卡特兰数,卡特兰数指数增长,必然不行。
《建立递归关系》
子问题状态的建模(很关键):令m[i][j]表示第i个矩阵至第j个矩阵这段的最优解。
显然如果i=j,则m[i][j]这段中就一个矩阵,需要计算的次数为0;
如果i>j,则m[i][j]=min{m[i][k]+m[k+1][j]+p[i-1]Xp[k]Xp[j]},其中k,在i与j之间游荡,所以i<=k<j ;
代码实现时需要注意的问题:计算顺序!!!
因为你要保证在计算m[i][j]查找m[i][k]和m[k+1][j]的时候,m[i][k]和m[k+1][j]已经计算出来了。
观察坐标的关系如下图:
实习代码1(java):
package test1;
public class MatrixMultiply {
/**
* @zhouhong
* 下面是矩阵连乘问题的动态规划算法
* 假设有6个矩阵:
* A1 A2 A3 A4 A5 A6
* 30*35 35*15 15*5 5*10 10*20 20*25 则matrixChain为
* {30,35,15,5,10,20,25} 结果为
* ((A1*(A2*A3))*((A4*A5)*A6))
*/
public static void traceback(String A[],int[][] s, int i, int j){
//s[i][j]记录了断开的位置,即计算A[i:j]的加括号方式为:
//(A[i:k])*(A[k+1:j])也就是(A[i:s[i][j]])*(A[s[i][j]+1;j])
if(i == j)
System.out.print(A[i-1] + " ");
else
{
System.out.print("(");
traceback(A,s,i,s[i][j]);//递归打印A[i:s[i][j]]的加括号方式
traceback(A,s,s[i][j]+1,j);
System.out.print(")");
}
//System.out.println("Mutiply A(" + i + "," + s[i][j] + ") and A(" + (s[i][j]+1)+ "," + j + ")");
}
public static void matrixChain(int []p, int [][]m, int [][]s)
{
int n = p.length - 1;
for( int i = 0; i <= n; i++)
m[i][i] = 0;//单个矩阵相乘次数为0
for(int r = 2; r <= n; r++ )//r为连乘的矩阵的个数
{
for(int i = 1;i <= n - r + 1; i++)//i就是连续r个矩阵的第一个
{
int j = i + r -1;//j就是连续r个矩阵的最后一个
m[i][j] = m[i+1][j] + p[i-1]*p[i]*p[j];//初始化m[i][j],即为:A[i:i]*A[i+1,j], s[i][j]=i (也就是:k= i);
s[i][j] = i;//初始化s[i][j]
//求m[i][j],m[i][j]就是Ai...Aj这j-i+1个矩阵连乘需要的最少的乘法次数
for(int k = i + 1; k < j; k++){
int t = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j];
if(t < m[i][j])//寻找最小指
{
m[i][j] = t;
s[i][j] = k;//记录划分标记
}
}
}
}
//n个矩阵连乘的最少相乘次数
System.out.println("给定的"+n+"个矩阵连乘的最少相乘的次数为:"+m[1][n]);
// printM(m);
// printS(s);
}
/* private static void printM(int[][] m){
for(int i = 1; i < 7; i++){
for(int j = 1; j < 7; j++){
System.out.println(m[i][j] + "\t");
}
System.out.println();
}
}
private static void printS(int[][] s){
for(int i = 1; i < 7; i++){
for(int j = 1; j < 7; j++){
System.out.println(s[i][j] + "\t");
}
System.out.println();
}
}
*/
public static void main(String[] args) {
int[][] m = new int[7][7];
int[][] s = new int[7][7];
int[] p = new int[] {30,35,15,5,10,20,25};
String[] A = new String[] {"A1","A2","A3","A4","A5","A6"};
matrixChain(p,m,s);
traceback(A,s,1,6);
}
}
输出结果:
给定的6个矩阵连乘的最少相乘的次数为:15125
((A1 (A2 A3 ))((A4 A5 )A6 ))
实习代码2(C++):
/*
问题描述:计算n个矩阵连乘所需的最少乘法次数
*/
#include <iostream>
#include <string>
using namespace std;
int Matrix_chain_Multiply(int p[],int n,int m[][8],int s[][8])
{
//这里的n是数组p[]的元素的个数,比矩阵个数多了一个
n -= 1;
int i,j,r,k,q;
for(i = 1; i <= n; i++)
m[i][i] = 0;//单个矩阵相乘次数为0
for(r = 2; r <= n; r++)
{
for(i = 1; i <= n -r + 1; i++)
{
j = i + r -1;
m[i][j] = 9999999;
//m[i][j] = m[i+1][j] + p[i-1]*p[i]*p[j];
//s[i][j] = i;
for(k = i; k < j; 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 m[1][n];
}
void Print_Matrix_Chain(string A[], int s[][8],int i,int j)
{
//输出最优解
if(i == j)
cout<<A[i-1]<<" ";
else
{
cout<<"(";
Print_Matrix_Chain(A,s,i,s[i][j]);
Print_Matrix_Chain(A,s,s[i][j]+1,j);
cout<<")";
}
}
int main()
{
int p[7] = {30,35,15,5,10,20,25};
int m[8][8],s[8][8];
int min = Matrix_chain_Multiply(p,7,m,s);
cout<<"上述6个矩阵连乘,最少需要做"<<min<<"次乘法运算"<<endl;
string A[6] = {"A1","A2","A3","A4","A5","A6"};
cout<<"最优的全加括号形式为:";
Print_Matrix_Chain(A,s,1,6);
cout<<endl;
return 0;
}
输出结果:
上述6个矩阵连乘,最少需要做15125次乘法运算
最优的全加括号形式为:((A1 (A2 A3 ))((A4 A5 )A6 ))