一、问题描述
给定n个矩阵的链(A1,A2,…,An),矩阵A的规模为pi-1 x pi (1<=i<=n),求完全括号化方案,使得计算乘积A1A2….An所需标量乘法次数最少。
注意:求解矩阵链乘法问题并不是要真正进行矩阵相乘运算,我们的目标只是确定代价最低的计算顺序。确定最优计算顺序所花费的时间通常要比随后真正进行矩阵相乘所节省的时间要少。
二、动态规划分析过程
1.最优括号化方案的结构
假设现在要计算AiAi+1….Aj的值,计算Ai…j过程当中肯定会存在某个k值(i<=k
/*
自底向上方法。j-i+1个矩阵链相乘的最优计算代价m[i,j]只依赖于那些少于j-i+1个矩阵链相乘的最优计算代价
先解决子问题,再层层向上解决子问题的原问题
计算公式: min{ m[i][k]+m[k+1][j]+p[i-1]p[k]p[j] }(i<=k<j)
*/
void matrix_chain_order(int *p, int length, int **m, int **s)
{
int i, j, k, l;
int n = length - 1;
for (i = 0; i <= n; i++)
{
m[i][i] = 0;
}
for (l = 2; l <= n; l++) //当前矩阵链长度
{
for (i = 1; i <= n - l + 1; i++) //从第一个矩阵开始算起,计算长度为l的最少计算代价
{
j = i + l - 1; //长度为l时的最后一个元素的下标值
m[i][j] = INT_MAX;
for (k = i; k <= j - 1; k++) //寻找最优的分割点k值,把矩阵链分割成Ai Ai+1...Ak和Ak+1 Ak+2...Aj
{
int q = m[i][k] + m[k + 1][j] + p[i - 1] * p[k] * p[j];
if (q < m[i][j])
{
m[i][j] = q; //记录当前长度l的最小代价
s[i][j] = k; //记录当前的括号位置,即矩阵的编号
}
}
}
}
}
2.打印最优括号化输出方案
//输出最优括号化方案
void print_optimal_parents(int **s, int i, int j)
{
if (i == j)
printf("A%d", i);
else
{
printf("(");
print_optimal_parents(s, i, s[i][j]);
print_optimal_parents(s, s[i][j] + 1, j);
printf(")");
}
}
3.测试代码
#include"matrix_chain_order.h"
#include"heap.h"
#define length 7
int main()
{
int p[length] = { 30, 35, 15, 5, 10, 20, 25 };
int **m = new_heap(length, length); //开辟内存
int **s = new_heap(length, length); //开辟内存
matrix_chain_order(p, length, m, s);
for (int i = 1; i <= length - 1; i++) //打印最优计算代价
{
for (int j = 1; j <= length - 1; j++)
printf("%d ", m[i][j]);
printf("\n");
}
printf("The result is:\n");
print_optimal_parents(s, 1, length - 1); //打印最优括号化方案
printf("\n");
delete_heap(m, length); //释放内存
delete_heap(s, length);
return 0;
}
4.运行结果
四、总结
动态规划解决问题关键是分析过程,难度在于如何发现其子问题的结构及子问题的递归解。这个需要多多思考,多多实践,不是一时半会就能快速挖掘最优子结构。实现过程还有就是数组下标的问题也比较蛋疼,一不小心就会越界,需要仔细处理,出了问题就要耐心debug.