矩阵连乘也是经典的动态规划的例子,而且能比较明显的把原来子问题的复杂度从指数级降为O(n^3),算是效果明显。
【分析】
算法的分析请见参考资料,我就不重复写了。就是写代码的时候需要注意两点:
(1)这个程序是自底向上的动态规划。因为我们在一个段[ i : j ]中进行划分的时候,那么必须下面的每一个小段必须已经生成了。如下图所示:
所以不能采用传统的那种i和j从头到尾的遍历(这种双重循环一般只适合于从前向后生成,比如最长公共子序列等动态规划)。
(2)一开始对书中的递推式m[i][j] = m[i][k] + m[k][j] + pi-1 * pk * pj中的后面三项很不理解。为什么会是三项呢?书中矩阵Ai的行数是pi-1,那么第k项矩阵的行数和列数就分别是pk-1和pk。由于是把从i到j的矩阵分为两段,第一段是从i到k,第二段是从k+1到j。所以第一段计算完之后形成的矩阵应该是pi-1行和pk列;第二段计算完之后形成的矩阵应该是pk行和pj列(开头是第pk+1个矩阵),所以根据矩阵相乘的性质,这两个(分段)矩阵相乘的代价就是pi-1 * pk * pj了。
【代码】
下面是实现的C++代码,注意:代码中的循环方式;以及另一处notice。
#include <iostream>
#include <vector>
#include <algorithm>
#include <iomanip>
using namespace std;
typedef vector<int> vi;
typedef vector<vi> Matrix;
struct aMatrix
{
aMatrix( int r, int c ){ row = r; col = c; }
int row; // the number of rows
int col; // the number of columns
};
// add brackets to the matrix multiplication
void addBrackets( Matrix& sep, int start, int end );
// fill in the minimum matrix and the separate point matrix
void MinMatrixMultiply( Matrix& min, vector<aMatrix>& p, Matrix& sep, int size );
//print function
void printMatrix( const Matrix& m );
int main()
{
vector<aMatrix> p;
p.push_back( aMatrix( 30, 35 ) );
p.push_back( aMatrix( 35, 15 ) );
p.push_back( aMatrix( 15, 5 ) );
p.push_back( aMatrix( 5, 10 ) );
p.push_back( aMatrix( 10, 20 ) );
p.push_back( aMatrix( 20, 25 ) );
int size = p.size();
Matrix min( size, vector<int>( size, 0 ) );
Matrix sep( size, vector<int>( size, 0 ) );
MinMatrixMultiply( min, p, sep, size );
printMatrix( min );
addBrackets( sep, 0, 5 );
}
void MinMatrixMultiply( Matrix& min, vector<aMatrix>& p, Matrix& sep, int size )
{
//because the matrix is initialized to all zero, so here we don't set min[i][i] to zero
/*
** Notice!
** We can't use the usual way of i,j loop( i from 0 to n, j from i to n )
** because we use dynamic programming in a bottom-up way
** Now, i is the beginning, j is the end, while r is the distance from i to j
** so r is increasing from 1 to n.
*/
for( int r = 1; r < size ; r++ )
{
for( int i = 0 ; i < size - r ; i++ )//i is the beginning
{
int j = i + r;// j is the end
/*
** Notice !!!
** Now we can try all the separate points from i to j
** but we must initialize the min[i][j] by try k = i.
** otherwise the t has no value to compare to.
*/
min[i][j] = min[i][i] + min[i+1][j] + p[i].row * p[i].col * p[j].col;
sep[i][j] = i;
for( int k = i + 1; k < j ; k++ )
{
int t = min[i][k] + min[k+1][j] + p[i].row * p[k].col * p[j].col;
//save the minimum
if( t < min[i][j] )
{
min[i][j] = t;
sep[i][j] = k;
}
}
}
}
}
void printMatrix( const Matrix& m )
{
int size = m.size();
for( int i = 0; i < size ; i++ )
{
for( int j = 0 ; j < size ; j++ )
{
cout<<std::setw(5)<<m[i][j]<<" ";
}
cout<<endl;
}
}
void addBrackets( Matrix& sep, int start, int end )
{
if ( start == end )
return ;
addBrackets( sep, start, sep[start][end] );
addBrackets( sep, sep[start][end]+1, end );
// print the first half
cout<< "multiply (A"<<start<<" to A"<<sep[start][end]<<")";
// print the second half
cout<<" and (A"<<sep[start][end]+1<<" to A"<<end<<")"<<endl;
}
#include <vector>
#include <algorithm>
#include <iomanip>
using namespace std;
typedef vector<int> vi;
typedef vector<vi> Matrix;
struct aMatrix
{
aMatrix( int r, int c ){ row = r; col = c; }
int row; // the number of rows
int col; // the number of columns
};
// add brackets to the matrix multiplication
void addBrackets( Matrix& sep, int start, int end );
// fill in the minimum matrix and the separate point matrix
void MinMatrixMultiply( Matrix& min, vector<aMatrix>& p, Matrix& sep, int size );
//print function
void printMatrix( const Matrix& m );
int main()
{
vector<aMatrix> p;
p.push_back( aMatrix( 30, 35 ) );
p.push_back( aMatrix( 35, 15 ) );
p.push_back( aMatrix( 15, 5 ) );
p.push_back( aMatrix( 5, 10 ) );
p.push_back( aMatrix( 10, 20 ) );
p.push_back( aMatrix( 20, 25 ) );
int size = p.size();
Matrix min( size, vector<int>( size, 0 ) );
Matrix sep( size, vector<int>( size, 0 ) );
MinMatrixMultiply( min, p, sep, size );
printMatrix( min );
addBrackets( sep, 0, 5 );
}
void MinMatrixMultiply( Matrix& min, vector<aMatrix>& p, Matrix& sep, int size )
{
//because the matrix is initialized to all zero, so here we don't set min[i][i] to zero
/*
** Notice!
** We can't use the usual way of i,j loop( i from 0 to n, j from i to n )
** because we use dynamic programming in a bottom-up way
** Now, i is the beginning, j is the end, while r is the distance from i to j
** so r is increasing from 1 to n.
*/
for( int r = 1; r < size ; r++ )
{
for( int i = 0 ; i < size - r ; i++ )//i is the beginning
{
int j = i + r;// j is the end
/*
** Notice !!!
** Now we can try all the separate points from i to j
** but we must initialize the min[i][j] by try k = i.
** otherwise the t has no value to compare to.
*/
min[i][j] = min[i][i] + min[i+1][j] + p[i].row * p[i].col * p[j].col;
sep[i][j] = i;
for( int k = i + 1; k < j ; k++ )
{
int t = min[i][k] + min[k+1][j] + p[i].row * p[k].col * p[j].col;
//save the minimum
if( t < min[i][j] )
{
min[i][j] = t;
sep[i][j] = k;
}
}
}
}
}
void printMatrix( const Matrix& m )
{
int size = m.size();
for( int i = 0; i < size ; i++ )
{
for( int j = 0 ; j < size ; j++ )
{
cout<<std::setw(5)<<m[i][j]<<" ";
}
cout<<endl;
}
}
void addBrackets( Matrix& sep, int start, int end )
{
if ( start == end )
return ;
addBrackets( sep, start, sep[start][end] );
addBrackets( sep, sep[start][end]+1, end );
// print the first half
cout<< "multiply (A"<<start<<" to A"<<sep[start][end]<<")";
// print the second half
cout<<" and (A"<<sep[start][end]+1<<" to A"<<end<<")"<<endl;
}
[参考文献]
王晓东。《计算机算法设计与分析》。P49
以及 http://blog.csdn.net/liguisen/archive/2008/03/08/2158127.aspx