矩阵连乘问题讲课稿.docx
《矩阵连乘问题讲课稿.docx》由会员分享,可在线阅读,更多相关《矩阵连乘问题讲课稿.docx(16页珍藏版)》请在冰豆网上搜索。
矩阵连乘问题讲课稿
矩阵连乘问题
矩阵连乘问题:
(1)直接递归思路
(2)备忘录思路
(3)动态规划思路
(1)直接递归算法
(2)备忘录算法
(3)动态规划算法
(1)直接递归算法程序
(2)备忘录算法程序
(3)动态规划算法程序
1.描述矩阵连乘问题:
给定n个矩阵{
},其中
和
是可乘的,i=1,2,…,n-1。
考察这n个矩阵的连乘积
。
由于矩阵乘法具有结合律,故计算矩阵的连乘积可以有许多不同的计算次序。
这种计算次序可以用加括号的方式来确定。
若一个矩阵连乘积的计算次序完全确定,也就是说连乘积已完全加括号,则可依次序反复调用2个矩阵相乘的标准算法计算出矩阵连乘积。
完全加括号的矩阵连乘可递归地定义为:
(1)单个矩阵是完全加括号的;
(2)矩阵连乘积A是完全加括号的,则A可表示为2个完全加括号的矩阵连乘B和C的乘积并加括号,即A=(BC)。
矩阵A和B可乘的条件是矩阵A的列数等于矩阵B的行数。
若A是一个p×q的矩阵,B是一个q×r的矩阵,那么C=A×B就是一个p×r矩阵。
它的计算是三重循环的,计算量是pqr。
如果加括号后矩阵的量是不同的,所以我们的问题就是要讨论如何给连乘的矩阵加括号才能使矩阵的计算量最少。
穷举搜索法:
对于n个矩阵的连乘积,设有不同的计算次序P(n)。
由于可以先在第k个和第k+1个矩阵之间将原矩阵序列分为两个矩阵子序列,k=1,2,...,n-1;然后分别对这两个矩阵子序列完全加括号;最后对所得的结果加括号,得到原矩阵序列的一种完全加括号方式。
由此可得P(n)的递归式如下:
1n=1
P(n)=
n>1
解此递归方程可得,P(n)=C(n-1),而C(n)是一个指数增长的函数。
因此穷举搜索法不是一个有效的算法。
以下将用三种方法来解决矩阵连乘问题的最优加括号方式以及最优解。
将矩阵连乘积
简记为A[i:
j]。
考察计算A[1:
n]的最优计算次序。
这个问题的一个关键特征是:
计算A[1:
n]的最优次序包含的计算矩阵子链A[1:
k]和A[k+1:
n]的次序也是最优的。
这是因为:
定义矩阵Ai的维数为pi-1×pi,则A[i:
k]的计算次数为pi-1×pk,A[k+1,j]的计算次数为pk×pj,而这两个总的矩阵最后相乘时的计算量是固定的,为pi-1×pk×pj。
所以,矩阵连乘计算次序问题的最优解包含着其子问题的最优解。
这种性质称为最优子结构性质。
(1)、直接递归的思路:
记计算A[i:
j],1≤i≤j≤n,所需最少数乘次数为m[i][j],则原问题的最优质为m[1][n]。
由分析得知:
m[i][j]可以递归的定义为:
0i=j
m[i][j]=
im[i][j]给出了最优值,即计算A[i][j]所需的最少数乘次数。
同时还确定了计算A[i:
j]的最优次序中的断开位置k,也就是说,对于这个k有
m[i][j]=
若将对应于m[i][j]的断开位置k记s[i][j],在计算出最优值m[i][j]后,可递归地由s[i][j]构造出相应的最优解。
可以证明该算法的计算时间T(n)有指数下界,由算法的递归部分可得:
O
(1)n=1
T(n)≥
1+
n>1
因此,当n>1时,T(n)≥n+2
据此,可用数学归纳法证明T(n)≥2n-1=
。
直接递归法的计算时间随n的增长指数增长。
(2)、备忘录方法的思路:
备忘录方法为每个子问题建立一个记录项,初始化时,该记录项存入一个特殊的值,表示该问题尚未求解。
在求解过程中,对每个待求的子问题,首先查看其相应的记录项。
若记录项中存储的是初始化时存入的特殊值,则表示该问题第一次遇到,此时计算出该子问题的解,并保存在相应的记录项中,以备以后查看。
若记录项中存储的不是初始化存入的特殊值,(比如初始化为-1,解答后赋值为0),则表示该问题已被计算过,其相应的记录项中存储的应该是该子问题的解答。
此时,只要从记录相中取出该子问题的解答即可,而不必重新计算。
备忘录方法的计算量:
因为是要计算m[i][j],因此只要从n个变量中任意选出2个分别作为i,j,则共有
种选法,即有
个子问题;当i=j时有n种选法,所以总的子问题就为:
+n=
个。
每填入一个记录项,就要花费O(n)的时间,所以备忘录方法的时间复杂度为O(n3)。
(3)、动态规划方法的思路:
(以6个矩阵相乘为例)
m[i][j]
1
2
3
4
5
6
1
0
2
x
0
3
x
x
0
4
x
x
x
0
5
x
x
x
x
0
6
x
x
x
x
x
0
注意,在m[i][j]中,如果i>j是没有意义的,因此在表格中都即为x;而且,如果i=j,则代表单个矩阵,所以m[i][i]=1.
根据直接递归的方法的思路,如果要求m[i][j],就必须要求m[i][k]和m[k+1][j],根据m[i][j]的矩阵,则如果要求解m[1][2],则需要知道m[1][1]和m[1][2];如果要求解m[1][3],则要知道m[1][1]、m[1][2]和m[1][1]和m[2][3];以此类推。
通过此规律可以总结出要求某一个元素,就要知道其左方的所有元素的值和其下方的所有元素的值。
m[i][j]
1
2
3
4
5
6
1
0
2
x
0
3
x
x
0
4
x
x
x
0
5
x
x
x
x
0
6
x
x
x
x
x
0
动态规划就是按照上图所画的形式进行求解,从左下方求到右上方。
动态规划算法的计算量主要取决于程序中对行、列和加括号的位置k的三重循环。
循环体内的计算量为O
(1),而三重循环的总次数为O(n3)。
因此该算法的计算时间上界为O(n3)。
和备忘录的算法的时间复杂度一样,都比直接递归的穷举搜索法有效得多。
(1)直接递归算法:
intRecurMatrixChain(inti,intj)
{
if(i==j)return0;
intu=RecurMatrixChain(i,i)+RecurMatrixChain(i+1,j)+p[i-1]*p[i]*p[j];//递归,p[]为维数
s[i][j]=i;//记录加括号的位置
for(intk=i+1;k{
intt=RecurMatrixChain(i,k)+RecurMatrixChain(k+1,j)+p[i-1]*p[k]*p[j];//递归
if(t
}
return0;
}
(2)备忘录算法:
intMemoizedMatrixChain(intn,int**m,int**s)
{
for(inti=1;i<=n;i++)
for(intj=i;j<=n;j++)
m[i][j]=0;//把m[i][j]的矩阵的上三角全部赋值为0。
returnLookupChain(1,n);
}
intLookupChain(inti,intj)
{
if(m[i][j]>0)returnm[i][j];//如果是已经解决的问题,则标记记录项m[i][j]已经有值,且大于0,避免重复计算。
if(i==j)return0;
intu=LookupChain(i,i)+LookupChain(i+1,j)+p[i-1]*p[i]*p[j];//递归
s[i][j]=i;
for(intk=i+1;kintt=LookupChain(i,k)+LookupChain(k+1,j)+p[i-1]*p[k]*p[j];//递归
if(t
}
m[i][j]=u;
returnu;
}
(3)动态规划算法:
voidMatrixChain(int*p,intn,int**m,int**s)
{
for(inti=1;i<=n;i++)m[i][i]=0;
for(intr=2;r<=n;r++)
for(inti=1;i<=n-r+1;i++){
intj=i+r-1;//具体推导由来见下图
m[i][j]=m[i+1][j]+p[i-1]*p[i]*p[j];
s[i][j]=i;
for(intk=i+1;kintt=m[i][k]+[k+1][j]+p[i-1]*p[k]*p[j];
if(t}
}
}
下面的算法Traceback按算法MatrixChain计算出的断点矩阵s指示的加括方式输出计算A[i:
j]的最优计算次序:
voidTraceback(inti,intj,int**s)
{
if(i==j)return;
Traceback(i,s[i][j],s);//递归地对左边进行加括号
Traceback(s[i][j]+1,j,s);//递归地对右边进行加括号
cout<<"MultiplyA"<
cout<<"andA"<<(s[i][j]+1)<<","<}
程序源代码:
matrix.h
#include
usingnamespacestd;
#defineNUM100
intm[NUM][NUM],s[NUM][NUM],p[NUM];//m[][]代洙?
表括?
最?
小?
数簓乘?
次?
数簓的?
矩?
阵ó;?
s[][]记?
录?
的?
是?
最?
佳?
加ó括ぁ?
号?
的?
位?
置?
;?
p[]表括?
示?
矩?
阵ó的?
维?
数簓,A[i]的?
维?
数簓为ap[i-1]×áp[i].
intn;
//备?
忘?
录?
方?
法ぁ?
intLookupChain(inti,intj)
{
if(m[i][j]>0)
{
returnm[i][j];
}
if(i==j)
return0;
intu=LookupChain(i,i)+LookupChain(i+1,j)+p[i-1]*p[i]*p[j];
s[i][j]=i;
for(intk=i+1;k{
intt=LookupChain(i,k)+LookupChain(k+1,j)+p[i-1]*p[k]*p[j];
if(t
{
u=t;
s[i][j]=k;
}
}
m[i][j]=u;
returnu;
}
intMemoizedMatrixChain(intn,int**m,int**s)
{
for(inti=1;i<=n;i++)
for(intj=i;j<=n;j++)
m[i][j]=0;//把?
m[i][j]的?
矩?
阵ó的?
上?
三▂角?
全?
部?
赋3值μ为a0。
£
returnLookupChain(1,n);
}
//动ˉ态?
规?
划?
法ぁ?
voidMatrixChain()
{
for(inti=1;i<=n;i++)
{
m[i][i]=0;
}
for(intr=2;r<=n;r++)
{
for(inti=1;i<=n-r+1;i++)
{
intj=r+i-1;
m[i][j]=m[i][i]+m[i+1][j]+p[i-1]*p[i]*p[j];
s[i][j]=i;
for(intk=i+1;k{
inttemp=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
if(temp{
m[i][j]=temp;
s[i][j]=k;
}
}
}
}
}
//直±接ó递蘗归é法ぁ?
intRecurMatrixChain(inti,intj)
{
if(i==j){return0;}
intu=RecurMatrixChain(i,i)+RecurMatrixChain(i+1,j)+p[i-1]*p[i]*p[j];//递蘗归é,?
p[]为a维?
数簓
s[i][j]=i;//记?
录?
加ó括ぁ?
号?
的?
位?
置?
for(intk=i+1;k{
intt=RecurMatrixChain(i,k)+RecurMatrixChain(k+1,j)+p[i-1]*p[k]*p[j];//递蘗归é
if(t
哪?
个?
值μ更ü小?
,?
选?
取?
哪?
个?
}
returnu;
}
voidTraceback(inti,intj)
{
if(i==j)//如?
果?
只?
有瓺一?
个?
矩?
阵ó,?
则ò直±接ó输?
出?
{
cout<<"A"<
}
elseif(i+1==j)//加ó括ぁ?
号?
输?
出?
{
cout<<"(A"<
}
else
{
cout<<"(";
Traceback(i,s[i][j]);
Traceback(s[i][j]+1,j);
cout<<")";
}
}
MatrixMultiply.cpp
#include
#include"matrix.h"
usingnamespacestd;
//intn;//定¨义?
一?
个?
全?
局?
变?
量?
intmain()
{
L:
intchoice;
cout<<"请输入相乘的矩阵的个数n=";
cin>>n;
cout<<"请输入第1个矩阵行数和第1个矩阵到第"<";for(inti=0;i<=n;i++)
{
cin>>p[i];
}
cout<cout<<"请选择矩阵连乘的算法:
"<cout<<"1.备忘录算法"<cout<<"2.直接递归算法"<cout<<"3.动态规划算法"<cout<<"4.重新输入矩阵"<cout<<"5.退出程序"<cout<cout<<"请输入选择的编号:
";
cin>>choice;
cout<while(choice!
=5){
switch(choice){
case1:
LookupChain(1,n);
cout<<"动态规划算法:
"<cout<<"矩阵连乘的最优值为:
"<cout<<"矩阵连乘的最优解为:
";
Traceback(1,n);
cout<break;
case2:
RecurMatrixChain(0,n);
cout<<"动态规划算法:
"<cout<<"矩阵连乘的最优值为:
"<cout<<"矩阵连乘的最优解为:
";
Traceback(1,n);
cout<break;
case3:
MatrixChain();
cout<<"动态规划算法:
"<cout<<"矩阵连乘的最优值为:
"<cout<<"矩阵连乘的最优解为:
";
Traceback(1,n);
cout<break;
case4:
gotoL;
break;
case5:
choice=4;
break;
default:
break;
}
cout<cout<<"请选择矩阵连乘的算法:
"<cout<<"1.备忘录算法"<cout<<"2.直接递归算法"<cout<<"3.动态规划算法"<cout<<"4.重新输入矩阵"<cout<<"5.退出程序"<cout<<"请输入选择的编号:
";
cin>>choice;
cout<}
cout<return0;
}
运行结果: