动态规划(二):矩阵链乘法

标签:
杂谈 |
一、问题描述
给定一个矩阵序列<A1,A2,…,An>,计算乘积A1A2…An。要求找出一个加全部括号的方式,使得标量乘法的次数最小。
加全部括号:
性质1,A1A2…An相乘,必须满足前一个矩阵的列数等于后一个矩阵的行数。
证明:假设Ai的列数为pi,i=1,…,n。下面我们来看各矩阵的行数。
当n=2时,根据矩阵乘法的定义可知,A1的列数等于A2的行数。因此,A2的行数等于p1。
当n=3时,因为A1A2相乘得到的新矩阵的列数为p2,而A1A2和A3可以相乘,因此A3的行数为p2。
依次类推,Ai(i≥2)的行数为pi-1,得证。
性质2,序列中的任何n个相邻矩阵都可以直接相乘,n≥2。
证明:
根据性质1可知,任意两个相邻矩阵都可以相乘,即n=2时,满足上述性质。
n=3时,假设我们选择任意相邻矩阵Ai-1AiAi+1,根据矩阵性质,Ai-1Ai得到的新矩阵为pi-2×pi,而Ai+1为pi×pi+1,因此Ai-1Ai得到的矩阵可以与Ai+1相乘,即Ai-1AiAi+1可以相乘。由于这三个矩阵是任意选择的,因此,n=3时,满足上述性质。
依次类推,对于任意n>=2,均满足上述性质。得证。
加全部括号具体步骤如下:
步骤一:选取相邻的两个矩阵,加括号;
步骤二:将步骤一中的括号看做一个新的矩阵,放回原先位置,得到一个新的序列;
步骤三:如果新序列中矩阵个数等于1,则结束,否则,返回步骤一。
也可以按照下面步骤进行加括号:
步骤一:如果当前序列只有一个矩阵,则不需加括号,否则将整个序列最外层加一个括号,使其成为一个新序列;
步骤二:检查序列里面是否有两个以上矩阵;
步骤三:如果是,则在这些矩阵中找一个分界点,将其分为两个新序列,对这两个新序列分别从步骤一开始重复执行上述步骤。
对于任意两个矩阵AiAi+1相乘,其乘法次数为pi-1pi pi+1,不同的加全部括号,所需要的乘法次数可能相差很大。
假设现在有三个矩阵A1A2A3相乘,维数分别为:10×100,100×5,5×50。
1、如果我们采用如下方式加全部括号:
((A1A2)A3)
则
首先计算(A1A2),乘法次数为p0p1 p2,得到新矩阵的维数为p0× p2
计算((A1A2)A3),乘法次数为p0p2 p3,
总的计算次数为p0p1 p2+ p0p2 p3=10×100×5+10×5×50=7500
2、如果我们采用如下方式加全部括号:
(A1(A2 A3))
则
首先计算(A2 A3),乘法次数为p1 p2 p3,得到新矩阵的维数为p1× p3
计算(A1(A2 A3)),乘法次数为p0p1 p3,
总的计算次数为p1 p2 p3+ p0p1 p3=100×5×50+10×100×50=75000
第二种方式需要的次数是第二次的10倍!
二、问题分析:
由前面可知,一个序列如果只有一个矩阵,则只有一种方式加全部括号,如果有两个或两个以上的矩阵,则必然可以看做两个子序列的乘积,且这两个子序列也是加全部括号。我们用cost(i,j)表示序列Ai…Aj在最优加全部括号时的标量乘积次数,则
http://s12/middle/70ec9a6fhb9ed2baf748b&690
其中pi-1pk pj为子序列Ai…Ak与Ak+1…Aj相乘时的标量相乘次数。
三、问题求解
每一对满足1≤i≤j≤n的i和j都对应原问题的一个子问题,子问题个数为http://s3/middle/70ec9a6fhb9ed3616ea92&690,其中第一项表示i<j时的子问题个数,后一项表示i=j时的子问题个数。如果采用递归方法求解,则每个子问题都会被求解多次,因此,我们也采用自底向上的动态规划方式求解。
首先定义一个二维矩阵value,value[i][j]存放子序列Ai…Aj在最优加全部括号时的标量乘积次数,我们只使用j>=i的部分,且value[i][i]=0。
动态规划的实现步骤如下:
步骤一:令step=0;
步骤二:对于所有i=0,…,n,计算并保存value[i][i+step;
步骤三:若step=n-1,则结束,否则step=step+1,返回步骤二。
四、例题
假设现在有如下矩阵序列:
矩阵 |
维数 |
A1 |
30×35 |
A2 |
35×15 |
A3 |
15×5 |
A4 |
5×10 |
A5 |
10×20 |
A6 |
20×25 |
C语言实现如下:
#include <stdio.h>
#include <stdlib.h>
#define ArrS 6
//数组个数
int m_value(int i,int j,int value[][ArrS+1],int Num1,int dimentions[],int position[][ArrS+1]);
void show(int position[][ArrS+1],int i,int j);
int main()
{
}
int m_value(int i,int j,int value[][ArrS+1],int Num1,int dimentions[],int position[][ArrS+1])
{
}
void show(int position[][ArrS+1],int i,int j)
//用递归的方法去显示完整路径,调用的方式是 show(1,n)
{
}