动态规划 矩阵链乘法
2013-08-17 20:36 youxin 阅读(1497) 评论(0) 编辑 收藏 举报矩阵链乘问题描述
给定n个矩阵构成的一个链<A1,A2,A3,.......An>,其中i=1,2,...n,矩阵A的维数为pi-1pi,对乘积 A1A2...An 以一种最小化标量乘法次数的方式进行加全部括号。
注意:在矩阵链乘问题中,实际上并没有把矩阵相乘,目的是确定一个具有最小代价的矩阵相乘顺序。找出这样一个结合顺序使得相乘的代价最低。
3、动态规划分析过程
1)最优加全部括号的结构
动态规划第一步是寻找一个最优的子结构。假设现在要计算AiAi+1....Aj的值,计算Ai...j过程当中肯定会存在某个k值(i<=k<j)将Ai...j分成两部分,使得Ai...j的计算量最小。分成两个子问题Ai...k和Ak+1...j,需要继续递归寻找这两个子问题的最优解。
有分析可以到最优子结构为:假设AiAi+1....Aj的一个最优加全括号把乘积在Ak和Ak+1之间分开,则Ai..k和Ak+1..j也都是最优加全括号的。
2)一个递归解
设m[i,j]为计算机矩阵Ai...j所需的标量乘法运算次数的最小值,对此计算A1..n的最小代价就是m[1,n]。现在需要来递归定义m[i,j],分两种情况进行讨论如下:
当i==j时:m[i,j] = 0,(此时只包含一个矩阵)
当i<j 时:从步骤1中需要寻找一个k(i≤k<j)值,使得m[i,j] =min{m[i,k]+m[k+1,j]+pi-1pkpj} (i≤k<j)。
3)计算最优代价
虽然给出了递归解的过程,但是在实现的时候不采用递归实现,而是借助辅助空间,使用自底向上的表格进行实现。设矩阵Ai的维数为pi-1pi,i=1,2.....n。输入序列为:p=<p0,p1,...pn>,length[p] = n+1。使用m[n][n]保存m[i,j]的代价,s[n][n]保存计算m[i,j]时取得最优代价处k的值,最后可以用s中的记录构造一个最优解。书中给出了计算过程的伪代码。具体代码如下:
#include<iostream> using namespace std; #define maxvalue 1000000 void matrix_chain_order(int p[],int len,int **m,int **s) { int n=len-1; //A[i][i]只有一个矩阵,所以相乘次数为0,即m[i][i]=0; for(int i=1;i<=n;i++) { m[i][i]=0; } for(int L=2;L<=n;L++)//l为chain length { for(int i=1;i<=n-L+1;i++) ///从第一矩阵开始算起,计算长度为L的最少代价,不是从0 { int j=i+L-1; m[i][j]=maxvalue; for(int k=i;k<j;k++) { int q=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j]; if(q<m[i][j]) { m[i][j]=q; s[i][j]=k; } } } } } void print_optimal_parents(int **s,int i,int j) { if(i==j) { cout<<"A"<<i; } else { cout<<"("; print_optimal_parents(s,i,s[i][j]); print_optimal_parents(s,s[i][j]+1,j); cout<<")"; } } int main() { /* 现有矩阵A1(30×35)、A2(35×15)、A3(15×5)、A4(5×10)、A5(10×20)、A6(20×25),得到p=<30,35,15,5,10,20,25> */ int p[]={30,35,15,5,10,20,25}; int len=sizeof(p)/sizeof(int); cout<<len<<endl; int **m,**s; m=new int*[len]; s=new int*[len]; for(int i=1;i<len;i++) { m[i]=new int[len]; s[i]=new int[len]; } for(int i=1;i<len;i++) { for(int j=1;j<len;j++) { m[i][j]=0; s[i][j]=0; } } matrix_chain_order(p,len,m,s); cout<<"***********M为***********"<<endl; for(int i=1;i<len;i++) { for(int j=1;j<len;j++) { cout<<m[i][j]<<ends; } cout<<endl; } cout<<"***********S为***********"<<endl; for(int i=1;i<len;i++) { for(int j=1;j<len;j++) { cout<<s[i][j]<<ends; } cout<<endl; } print_optimal_parents(s,1,6); }
输出:
我们假设矩阵为A1,A2,A3A4A5A6
数组p[7]恰好存储了Ai(维度为p(i-1)*p(i))
上面的代码我们可以按照下面的方式理解:
首先看对角线,矩阵元素长度为1时,m[i][j]=0;因为一个元素没有乘法为0.
长度为2时有2个元素,可以是(1,2),(2,3),(3,4),(4,5),(5,6)
以此类推,
长度为6时只有一种选择了(1,6).
可以看到m[i][j]形成的上三角。
如果采用递归进行实现,则需要指数级时间Ω(2n),因为中间有些重复计算。递归是完全按照第二步得到的递归公式进行计算,递归实现如下所示:
int recursive_matrix_chain(int p[],int len,int **m,int **s,int i ,int j) { if(i==j) { m[i][j]=0; } else { int k; m[i][j]=maxvalue; for(k=i;k<j;k++) { int tmp=recursive_matrix_chain(p, len,m,s,i,k)+recursive_matrix_chain(p,len,m,s,k+1,j)+ p[i-1]*p[k]*p[j]; if(tmp<m[i][j]) { m[i][j]=tmp; s[i][j]=k; } } } return m[i][j]; }
只需改下调用方法即可:
// matrix_chain_order(p,len,m,s); 改为
cout<< recursive_matrix_chain(p,len,m,s,1,6)<<endl;
改进递归:
递归重复了很多计算,我们可以做一个备忘录,其思想就是备忘(memorize)原问题的自然但低效的递归算法。
加入备忘的递归算法为每一个子问题的解在表中记录一个表项,开始时,美国表现最初都含有一个特殊的值,以表示该值有待填入。当在递归算法的执行中第一次遇到一个子问题时,就计算它的解并填入表中以后。以后每次遇到子问题时,只要查看返回表中先前填入的值即可。
int memoized_matrix_chain(int p[],int len,int **m,int **s) { int n=len-1; for(int i=1;i<=n;i++) for(int j=i;j<=n;j++) m[i][j]=maxvalue; return lookup_chain(p,len,m,s,1,n); } int lookup_chain(int p[],int len,int **m,int **s,int i,int j) { if(m[i][j]<maxvalue) { return m[i][j]; } if(i==j) { m[i][j]=0; } else { for(int k=i;k<=j-1;k++) { int q=lookup_chain(p, len,m,s,i,k)+lookup_chain(p,len,m,s,k+1,j)+ p[i-1]*p[k]*p[j]; if(q<m[i][j]) { m[i][j]=q; s[i][j]=k; } } } return m[i][j]; }
像动态规划的复杂度一样,
memoized_matrix_chain的复杂度也是O(n^3)d .
参考了:http://www.cnblogs.com/Anker/archive/2013/03/10/2952475.html
http://www.cnblogs.com/weixliu/archive/2012/12/25/2832772.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
2012-08-17 hdu 1003 max sum
2011-08-17 建立WordPress博客网站——个人教程