首页 > 代码库 > 《数据结构与算法分析:C语言描述》复习——第十章“算法设计技巧”——矩阵连乘问题
《数据结构与算法分析:C语言描述》复习——第十章“算法设计技巧”——矩阵连乘问题
2014.07.07 15:47
简介:
给定N个矩阵,A1、A2、...、An,如果相邻矩阵的维度都满足相乘条件,如何组织这n-1次乘法的顺序,使得总共的乘法次数最少?
描述:
根据矩阵乘法的定义,如果矩阵X的维度是aXb,矩阵Y的维度是bXc。那么XY相乘需要的乘法次数是aXbXc。
这道题目是典型的动态规划问题。从使用者的角度来看,动态规划问题通常的应用情景主要有两个特点:
1. 暴力搜索能够得出答案,但速度实在太慢。如果用空间换时间,记录一些中间状态的话,可以极大程度地降低复杂度,减少重复计算。
2. 贪婪思路无法得出正确答案,所以尽管贪婪思路的代码通常很简洁,思路清晰,但既然不能解决问题,也就不能用了。
最常见的动态规划问题的描述一般都像这样:“用X维数组dp表示能得到的最优XXX,dp[i][j][k]...表示第i步时第j段时...的最优解。”
这样描述的特点,就是不论描述起来有多么冗长和复杂,基本思路都是把一整个问题分解为若干维度,每个维度分解为多步。每一次推导,只是走有限步。如果每次走s步,那么就会保证有s个初始状态,这样能够保证每一个状态的解都能被计算出。这个解一定是最优解。而同样的问题使用贪婪策略通常只能得到接近最优解的次优解。
分治法用递归的方法分解子问题,然后通过合并子问题来解决整体问题。动态规划则使用递推公式从当前状态推导下一状态。
比如下面这个式子:dp[i][j]=min(dp[i][j-1], dp[i-1][j])+1。我只是随手写了这个式子,而动态规划的递推公式通常都是dp[i]与dp[i-1]之间的关系式,或者有更多维度。而我们所期待的最终答案很可能就是dp[n][m]或者dp[n][n]。而dp[0][0]则是我们推导的起点,通常是已知的(有时你必须动动脑子才能想清楚这个已知值到底是几)。
于是,动态规划的起点、终点、中间状态、递推公式、维度等等概念大概是这么回事:
1. 维度:你要用一个几维数组来表示你的最优解呢?例如求出一个字符串中回文字串的个数,你可以用dp[i][j]表示字符串s[i,j]中所有回文子串的个数。这就是二维动态规划。
2. 起点:你开始递推之前,必须得知道dp[0]、dp[0][0]之类的等于几。这个过程有时会花很久,有时则是一念之间的事。
3. 中间状态:dp[i][j]代表什么?问题不同意义自然不同。总之它是一个中间状态,你必须很清楚它的意义。
4. 递推公式:从dp[i-1][j]、dp[i-1][j-1]、dp[i][j-1]如何推导出dp[i][j]呢?用递推公式。递推公式怎么得出的?用你的草稿纸和笔,这就是脑细胞的用武之地了。
5. 终点:dp[n]、dp[n][m]、dp[n][m][p]等等。不论n、m、p等等字母代表什么,终点在哪儿肯定是一开始就确定的。这个不成问题。
最后,用三句话解决矩阵连乘问题:
1. 用dp[i][j]表示i号矩阵连乘到j号矩阵为止需要的最少乘法次数。
2. dp[0][n-1]是我们需要的最终结果。
3. 递推公式请直接看代码。
实现:
1 // Ordering matrix multiplication, a typical dynamic programming problem. 2 // Description: 3 // You have a list of matrices M1, M2, ..., Mn to multiply. 4 // Each with dimension (X1, Y1), (X2, Y2), ..., (Xn, Yn) 5 // Of course, Yi == X(i + 1) 6 // You can arrange the order of matrix multiplications, 7 // to achieve minimal number of multiplications. 8 // For example: A * B * C can be A * (B * C) or (A * B) * C, 9 // they might require different number of multiplications.10 #include <iostream>11 #include <vector>12 using namespace std;13 14 const int INF = 1000000000;15 16 int orderingMatrixMultiplication(const vector<int> &dimension)17 {18 int n = (int)dimension.size() - 1;19 vector<vector<int> > dp;20 21 dp.resize(n, vector<int>(n, INF));22 23 int i, j, k;24 25 for (i = 0; i < n; ++i) {26 dp[i][i] = 0;27 }28 29 for (i = 1; i < n; ++i) {30 for (j = 0; j + i < n; ++j) {31 for (k = j; k < j + i; ++k) {32 dp[j][j + i] = min(dp[j][j + i], dp[j][k] + dp[k + 1][j + i]33 + dimension[j] * dimension[k + 1] * dimension[j + i + 1]);34 }35 }36 }37 38 int result = dp[0][n - 1];39 dp.clear();40 41 return result;42 }43 44 int main()45 {46 int i;47 int n;48 vector<int> v;49 50 while (cin >> n && n > 0) {51 v.resize(n + 1);52 for (i = 0; i < n + 1; ++i) {53 cin >> v[i];54 }55 cout << orderingMatrixMultiplication(v) << endl;56 }57 58 return 0;59 }