最优二叉查找树的一道思考习题
同最优二叉查找树一样,矩阵连乘问题也是一个卡特兰数问题(其动态规划的构造过程都很像)
----------------------------------------------------------------------------------------------------------------------------------------------------
分析解答:
a,铺垫的数学知识
首先要搞清楚矩阵相乘是怎么乘的:
1)对于连续的n个矩阵相乘 A1 * A2 *A3.........An,其乘法顺序可以是任意的,可以在上面加括号,改变做乘法的顺序,例如 A*B*C三个矩阵相乘可以A*(B*C)
也可以直接按从左到右的顺序。连续的两个矩阵的位数必须满足m*p,p*n才能相乘,且相乘后的结果是个m*n的矩阵。(线性代数的知识)
2)对于2个m*p,p*n的矩阵相乘,共做乘法次数为 m*n*p 次。
这是预备知识,知道矩阵连续的乘法的运算次数跟运算顺序有关后,就很容易举出例子了,略。
b,卡特兰数个,证明很麻烦,有时间看了组合数学再来看
------------------------------------------------------------------------------------------------------------------------
c,重点是要解决这个问题。
设 M[i , j]表示从第 i 个矩阵到第 j 个矩阵连乘的最少乘法次数,(i 从 0 编号),我们最终的目标是求 M[0 , n-1]。
Ai *.......Ak * Ak+1.....Aj
假设要得到这个式子的值(即从矩阵 i 连乘到矩阵 j),所作的最后一个矩阵乘法是在 矩阵 k 后(注意准确的描述位置)断开的(即左右都已乘运算好),那么容易得到
其递推式:
M[i , j] = min{ M[i , k] + M[k+1 , j] + di * dk+1 * dj+1} i <= k <= j-1
其中 di 是矩阵 Ai 的第一维,dk+1是断开处矩阵 Ak 的第二维(即Ak+1的第一维,是一样的),dj+1是最后一个矩阵 Aj 的第二维。
得到这个式子也是一个 逆向思维的过程。
-------------------------------------------------------------------------------
可以用矩阵连乘的动态规划构造过程与最优二叉查找树比较下,发现其构造非常相似(在前面一篇dp之什么叫做professional中提到过,不再详述)
实现:
初始条件:M[i , i] = 0
填表顺序:鉴于其递推式与最优二叉查找树相似,填表顺序也是按对角线的,自己画画就知道了。
代码也跟最优二叉查找树的控制逻辑相似:
package
Section8;
/* 第八章 动态规划 课后习题:矩阵连乘 */
public class MatEven {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
int [] Dim = { 30 , 35 , 35 , 15 , 15 , 5 , 5 , 10 , 10 , 20 , 20 , 25 };
int result = MatEven(Dim);
System.out.println( " \n动态规划求的的最优策略相乘顺序导致的最少乘法数为: " + result);
}
public static int MatEven( int [] Dim){
// 接受n个矩阵的维度数组Dim大小为2n
int n = Dim.length / 2 ; // 有n个矩阵,编号0...n-1,编号为k的矩阵的维数是Dim[2k] * Dim[2k+1]
int [][] Result = new int [n][n]; // 最小代价矩阵
// 初始化
for ( int i = 0 ;i < n;i ++ )
Result[i][i] = 0 ;
// 沿对角线填矩阵
for ( int d = 1 ;d <= n - 1 ;d ++ ) // 共n-1条对角线需要填
{
for ( int i = 0 ;i <= n - d - 1 ;i ++ ) // 第d条对角线的第一个点横坐标为d
{
// int j = i - d;
int j = i + d; // 在第d条对角线上的点,横纵坐标的关系是j = i + d
// 这样就确定了一个位置(i,j)的坐标,然后来填(i,j)
int Min = 1000000000 ;
for ( int k = i;k <= j - 1 ;k ++ ) // 从第k个矩阵后面断开
{
// 动态规划状态转移方程
int temp = Result[i][k] + Result[k + 1 ][j] + (Dim[ 2 * i] * Dim[ 2 * k + 1 ] * Dim[ 2 * j + 1 ]);
if ( temp < Min)
Min = temp;
}
Result[i][j] = Min;
}
}
return Result[ 0 ][n - 1 ];
}
}
/* 第八章 动态规划 课后习题:矩阵连乘 */
public class MatEven {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
int [] Dim = { 30 , 35 , 35 , 15 , 15 , 5 , 5 , 10 , 10 , 20 , 20 , 25 };
int result = MatEven(Dim);
System.out.println( " \n动态规划求的的最优策略相乘顺序导致的最少乘法数为: " + result);
}
public static int MatEven( int [] Dim){
// 接受n个矩阵的维度数组Dim大小为2n
int n = Dim.length / 2 ; // 有n个矩阵,编号0...n-1,编号为k的矩阵的维数是Dim[2k] * Dim[2k+1]
int [][] Result = new int [n][n]; // 最小代价矩阵
// 初始化
for ( int i = 0 ;i < n;i ++ )
Result[i][i] = 0 ;
// 沿对角线填矩阵
for ( int d = 1 ;d <= n - 1 ;d ++ ) // 共n-1条对角线需要填
{
for ( int i = 0 ;i <= n - d - 1 ;i ++ ) // 第d条对角线的第一个点横坐标为d
{
// int j = i - d;
int j = i + d; // 在第d条对角线上的点,横纵坐标的关系是j = i + d
// 这样就确定了一个位置(i,j)的坐标,然后来填(i,j)
int Min = 1000000000 ;
for ( int k = i;k <= j - 1 ;k ++ ) // 从第k个矩阵后面断开
{
// 动态规划状态转移方程
int temp = Result[i][k] + Result[k + 1 ][j] + (Dim[ 2 * i] * Dim[ 2 * k + 1 ] * Dim[ 2 * j + 1 ]);
if ( temp < Min)
Min = temp;
}
Result[i][j] = Min;
}
}
return Result[ 0 ][n - 1 ];
}
}
上面用一个数组接受一个连乘的矩阵的维数,
例如连乘的矩阵维数是:30*35 35*15 15*5 5*10 10*20 20*25
用动态规划求解得到的最佳乘法次数是:
动态规划求的的最优策略相乘顺序导致的最少乘法数为:15125
直接返回矩阵的话就可以得到整个M[i , j]的值
如果按照从左到右的顺序做乘法,是远远不止这个次数的。
-------------------------------------------------------------------------
当然,再做一些处理,就还可以得到具体的次序,类似于最优二叉查找树,就是要记录动态规划产生的过程,略
----------------------------------------------------------------------------------------------
总结:
矩阵连乘问题是个卡特兰数问题
其动态规划的构造过程非常类似于最优二叉查找树
矩阵连乘的最有子结构是什么?