问题描述
给定若干个矩阵,寻找最优的相乘次序,使得乘法运算的次数最少,并输出对应的最少运算次数。比如现有三个矩阵ABC,维数分别为\(A:2\times10\),\(B:10\times2\), \(C:2\times10\) 。虽然(AB)C=A(BC) 结果是相等的,即与相乘次序没有关系。但是(AB)C乘法运算的次数为\(2\times10\times2+2\times2\times10=80\),而A(BC)为\(10\times2\times10+2\times10\times10=400\),显然(AB)C的运算次数更少,即效率更高。
思路
求解的关键在于如何将问题分解为若干子问题。我们想象在各个矩阵之间可以放上隔板,那么只要先分别求解左和右的最少乘法次数,再将隔板左右的两部分相乘,就可以得到当前分隔方法的最优解,最后通过比较各种分隔方法,就能得到当前长度的最优解,如下图中四种分隔中我们取最少的次数,即为长度5(5个连续矩阵)的最优解。由于分隔后的长度必定小于当前长度,如此处的子问题长度必定小于5,而子问题已经在之前的迭代过程求得,无需重复计算。通过下一部分的迭代过程展示能有更直观的理解。
迭代过程
给定5个矩阵: ABCDE ,维数分别为\(A:30\times35\) , \(B:35\times15\), \(C:15\times5\) , \(D:5\times10\), \(E:10\times20\) 。从矩阵链长度为1开始求解,然后求解长度为2的,最终到达5。dp[]数组(详见算法实现)的更新如下图所示。
为了更直观的理解,下图和上面是等效的。(符号说明:m[a,b]代表从序号为a到序号为b的矩阵链所需的最少乘法次数,特别地,m[a,a]代表a号矩阵本身,很明显m[a,a]=0。)
算法实现
arr[]数组用于记录矩阵链信息,其中n号矩阵对应的维数是arr[n-1]*arr[n]。动态规划的核心算法利用3个for循环,最外层控制矩阵链长度,下一层控制起始点,再下一层控制隔板的位置。最后左右合并的时候注意下标
的选择,如下图所示。
#include <iostream>
#include <limits>
#include <cstring>
using namespace std;
int dp[51][51]; //从1开始,不使用0
// output: Minimum number of multiplications is 11875
/*功能:寻找矩阵链最优的运算次数
返回值:相对应的运算次数 */
int MinMatrixMul(int arr[], int num){
int start, mid, end;
int times; //记录当前的最优解,即最少的运算次数
//dp[i][i]=0 已经在初始化的时候完成
//dp[i][j] 代表i号到j号矩阵链的运算次数
for (int length = 2; length < num; ++length) {
for (start = 1; start <= num-length; ++start) {
end = start + length - 1;
dp[start][end] = INT_MAX;
for (mid = start; mid < end; ++mid) {
times = dp[start][mid] + dp[mid+1][end] + arr[start-1]*arr[mid]*arr[end];
//如果找到当前长度下更优的解,立刻更新dp数组
if (times < dp[start][end]) {
dp[start][end] = times;
}
}
}
}
return dp[1][num-1];
}
int main() {
int num;
int arr[] = {30, 35, 15, 5, 10, 20};
memset(dp, 0, sizeof(dp));
num = sizeof(arr)/ sizeof(int);
cout << "Minimum number of multiplications is "<< MinMatrixMul(arr, num) << endl;
}
拓展:尝试写出计算顺序,即打印括号
思路:增加bracket数组,bracket[start][end]记录从start到end当中的mid,即加括号的地方。若start==end说明只有一个矩阵,直接输出。令name(使用引用
)从'A'开始,一旦输出一个,便执行++name。
#include <iostream>
#include <limits>
#include <cstring>
using namespace std;
int dp[51][51]; //从1开始,不使用0
int bracket[51][51]; //记录括号添加的位置
/*output:
* Minimum number of multiplications is 11875
* Order: ((A(BC))(DE))
* */
/*功能:打印括号
*返回值:无 */
void printBrackets(int start, int end, char &name){
if (start == end) {
cout << name;
++name;
return;
}
cout << "(";
printBrackets(start, bracket[start][end], name);
printBrackets(bracket[start][end]+1, end, name);
cout << ")";
}
/*功能:寻找矩阵链最优的运算次数
*返回值:相对应的运算次数 */
int MinMatrixMul(int arr[], int num){
int start, mid, end;
int times; //记录当前的最优解,即最少的运算次数
//dp[i][i]=0 已经在初始化的时候完成
//dp[i][j] 代表i号到j号矩阵链的运算次数
for (int length = 2; length < num; ++length) {
for (start = 1; start <= num-length; ++start) {
end = start + length - 1;
dp[start][end] = INT_MAX;
for (mid = start; mid < end; ++mid) {
times = dp[start][mid] + dp[mid+1][end] + arr[start-1]*arr[mid]*arr[end];
//如果找到当前长度下更优的解,立刻更新dp数组
if (times < dp[start][end]) {
dp[start][end] = times;
//更新添加括号的位置
bracket[start][end] = mid;
}
}
}
}
return dp[1][num-1];
}
int main() {
int num;
char name = 'A';
int arr[] = {30, 35, 15, 5, 10, 20};
memset(dp, 0, sizeof(dp));
num = sizeof(arr)/ sizeof(int);
cout << "Minimum number of multiplications is "<< MinMatrixMul(arr, num) << endl;
cout << "Order: " ;
printBrackets(1, num-1, name);
}
参考资料
http://www.geeksforgeeks.org/dynamic-programming-set-8-matrix-chain-multiplication/
http://www.geeksforgeeks.org/printing-brackets-matrix-chain-multiplication-problem/