对矩阵链加括号的方式会对乘积运算的代价产生巨大影响,现在使用动态规划来对矩阵链乘法问题进行求解。
矩阵链乘法问题可描述如下:给定n个矩阵的链<A1,A2,...,An>,矩阵Ai的规模为pi-1*pi(1 =< i =<n),求完全括号化方案,使得计算乘积A1A2…An所需标量乘法次数最少。
步骤1:最优括号化方案的结构特征
动态规划方法的第一步是寻找最优子结构,然后就可以利用这种子结构从子问题的最优解构造出原问题的最优解。现在给出本问题的最优子结构。假设AiAi+1…Aj的最优括号化方案的分割点在Ak和Ak+1之间。那么,继续对“前缀”子链AiAi+1…Ak进行括号化,我们应该直接采用独立求解它时所得的最优方案。对Ak+1Ak+2…Aj我们有相似的结论。
步骤2:一个递归求解方案
下面用子问题的最优解来递归地定义原问题最优解的代价。对矩阵链乘法问题,我们可以将所有1≤i≤j≤n确定AiAi+1…Aj的最小代价括号化方案作为子问题。令m[i][j]表示计算矩阵Ai..j所需要标量乘法次数的最小值,那么,原问题的最优解——计算A1..n所需的最低代价就是m[1][n]。递归求解公式如下所示:
步骤3:计算最优代价
实现代码如下所示:
void MATRIX_CHAIN_ORDER(int *p, int m[LEN][LEN], int s[LEN][LEN]) { //m[i,j]表示计算矩阵Ai.j所需要的标量乘法次数的最小值 //s[i,j]记录分割点 int i, j, k, l;//l表示矩阵链的长度 int q;//保存最小乘法运算次数 for(i = 0; i < LEN; i++)//只有一个矩阵,乘法运算次数为0 m[i][i] = 0; for(l = 2; l < LEN; l++) { for(i = 1; i < LEN-l+1; i++) { j = i+l-1; m[i][j] = MAX; 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; } } } } }
假设有如下的矩阵链:
则构成如下的矩阵规模序列:
根据动态规划,可求得如下的两个矩阵,m[i][j]表示计算矩阵Ai..j所需要标量乘法次数的最小值,而s[i][j]表示矩阵Ai..j所需要标量乘法次数的最小值的切割点:
如m[1][4] = 405,表示矩阵链A1A2A3A4的最小标量乘法次数为405次,s[1][4] = 2,表示最小标量乘法的切割法的切割点为2,即变为(A1A2)(A3A4)。这里要注意一点的是:在求m[1][4]时,其所有子问题都已求出。
结果打印函数:
void PRINT_OPTIMAL_PARENS(int s[LEN][LEN], int i, int j) { if(i == j) cout<<"A"<<i; else { cout<<"("; PRINT_OPTIMAL_PARENS(s,i,s[i][j]); PRINT_OPTIMAL_PARENS(s,s[i][j]+1,j); cout<<")"; } }
最后求得的结果为:
矩阵链乘法