算法导论------------------动态规划之矩阵链问题
给定有n个连乘矩阵的维数,要求计算其采用最优计算次序时所用的乘法次数,即所要求计算的乘法次数最少。例如,给定三个连乘矩阵{A1,A2,A3}的维数分别是10*100,100*5和5*50,采用(A1A2)A3,乘法次数为10*100*5+10*5*50=7500次,而采用A1(A2A3),乘法次数为100*5*50+10*100*50=75000次乘法,显然,最好的次序是(A1A2)A3,乘法次数为7500次。
分析:
矩阵链乘法问题描述:给定由n个矩阵构成的序列[A1,A2,...,An],对乘积A1A2...An,找到最小化乘法次数的加括号方法。
1)寻找最优子结构
此问题最难的地方在于找到最优子结构。对乘积A1A2...An的任意加括号方法都会将序列在某个地方分成两部分,也就是最后一次乘法计算的地方,我们将这个位置记为k,也就是说首先计算A1...Ak和Ak+1...An,然后再将这两部分的结果相乘。
最优子结构如下:假设A1A2...An的一个最优加括号把乘积在Ak和Ak+1间分开,则前缀子链A1...Ak的加括号方式必定为A1...Ak的一个最优加括号,后缀子链同理。一开始并不知道k的确切位置,需要遍历所有位置以保证找到合适的k来分割乘积。
2)构造递归解设m[i,j]为矩阵链Ai...Aj的最优解的代价,则
┌ 0 如果i = j
m[i,j] =
└ min(i≤k<j) {m[i,k] + m[k+1,j] + p[i-1] * p[k] *p[j] } 如果i<j
3)构建辅助表,解决重叠子问题从第二步的递归式可以发现解的过程中会有很多重叠子问题,可以用一个nXn维的辅助表m[n][n] s[n][n]分别表示最优乘积代价及其分割位置k 。
辅助表s[n][n]可以由2种方法构造,一种是自底向上填表构建,该方法要求按照递增的方式逐步填写子问题的解,也就是先计算长度为2的所有矩阵链的解,然后计算长度3的矩阵链,直到长度n;另一种是自顶向下填表的备忘录法,该方法将表的每个元素初始化为某特殊值(本问题中可以将最优乘积代价设置为一极大值),以表示待计算,在递归的过程中逐个填入遇到的子问题的解。
这里我使用c++中容器来解决,一般使用的数组下标带来的问题极其容易出差,还是c++比较方便,解决数组必须用常量来表示数组下标。不废话了,思路自己看算法导论树上的讲解,搞清解决问题原理写出代码就轻而易举。
#include<iostream> #include<vector> #include<algorithm> #include<iterator> using namespace std; pair<vector<vector<int>>,vector<vector<int>>> matrix_chain_order(vector<int> p) { int n = p.size(); vector<vector<int>> m(n, vector<int>(n, 0));//vector的vector相当于数组的数组,即二维数组m[][] vector<vector<int>> s(n, vector<int>(n, 0));//同上s[][] for (int len = 2; len < n; len++) { for (int i = 1; i < n - len + 1; ++i) { int j = i + len - 1; m[i][j] = 0x7fffffff; for (int k = i; k <= j-1; ++k) { 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; s[i][j] = k; } } } } return make_pair(m, s); } //打印最优解 void print_Optimal_Parens(vector<vector<int> > s, 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 << ")"; } } //一般递归方法 int recursive_matrix_chain(vector<int> p, int i, int j) { int n = p.size(); vector<vector<int>> m(n, vector<int>(n, 0)); if (i == j) return 0; m[i][j] = 0x7fffffff; for (int k = i; k < j; k++) { int q = recursive_matrix_chain(p, i, k) + recursive_matrix_chain(p, k + 1, j) + p[i - 1] * p[k] * p[j]; if (q < m[i][j]) m[i][j] = q; } return m[i][j]; } //带备忘的自顶向下的递归方法实现 int look_up_chain(vector<vector<int>> m, vector<int> p, int i, int j) { if (m[i][j] < 0x7fffffff)//如果m[i][j]不是无穷大则说明m[i][j]已经被下一层的LookUpChain计算出来 return m[i][j]; if (i == j) m[i][j] = 0; for (int k = i; k < j; k++) { int q = look_up_chain(m, p, i, k) + look_up_chain(m, p, k + 1, j) + p[i - 1] * p[k] * p[j]; if (q < m[i][j]) m[i][j] = q; } return m[i][j]; } int memoized_matrix_chain(vector<int> p,int s,int e) { int n = p.size(); vector<vector<int>> m(n, vector<int>(n, 0)); for (int i = 1; i < n; i++) { for (int j = i; j < n; ++j) { m[i][j] = 0x7fffffff; } } return look_up_chain(m, p, s, e); } int main() { vector<int> p = { 30, 35, 15, 5, 10, 20, 25 }; vector<vector<int>> m = matrix_chain_order(p).first; vector<vector<int>> s = matrix_chain_order(p).second; cout << "vector m:" << endl; for (size_t i = 0; i < m.size(); i++) { copy(m[i].begin(), m[i].end(), ostream_iterator<int>(cout, " ")); cout << endl; } cout << "vector s:" << endl; for (size_t i = 0; i <s.size(); i++) { copy(s[i].begin(), s[i].end(), ostream_iterator<int>(cout, " ")); cout << endl; } cout << "输出最优解:————>"; print_Optimal_Parens(s, 1, 6); cout << endl; cout << "--------------------------------------------------" << endl;; vector<vector<int>> m1 = matrix_chain_order(p).first; vector<int> p1 = { 30, 35, 15, 5, 10, 20, 25 }; cout << "普通递归方法求解:"; cout << recursive_matrix_chain(p1, 1, 6) << endl; cout << "带备忘的递归方法求解:"; cout << look_up_chain(m1, p1, 1, 6) << endl; }
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。