动态规划矩阵连乘算法
动态规划矩阵连乘算法

问题解析:由于矩阵乘法满足结合律,故计算矩阵的连乘积可以有许多不同的计算次序。
这种计算次序可以用加括号的方式来确定。
若一个矩阵连乘积的计算次序完全确定,也就是说该连乘积已完全加括号,则可以依此次序反复调用2个矩阵相乘的标准算法计算出矩阵连乘积。
完全加括号的矩阵连乘积可递归地定义为:(1)单个矩阵是完全加括号的;(2)矩阵连乘积A是完全加括号的,则A可表示为2个完全加括号的矩阵连乘积B和C的乘积并加括号,即A=(BC)例如,矩阵连乘积A1A2A3A4有5种不同的完全加括号的方式:(A1(A2(A3A4))),(A1((A2A3)A4)),((A1A2)(A3A4)),((A1(A2A3))A4),(((A1A2)A3)A4)。
每一种完全加括号的方式对应于一个矩阵连乘积的计算次序,这决定着作乘积所需要的计算量。
看下面一个例子,计算三个矩阵连乘{A1,A2,A3};维数分别为10*100 , 100*5 , 5*50 按此顺序计算需要的次数((A1*A2)*A3):10X100X5+10X5X50=7500次,按此顺序计算需要的次数(A1*(A2*A3)):10*5*50+10*100*50=75000次所以问题是:如何确定运算顺序,可以使计算量达到最小化。
算法思路:例:设要计算矩阵连乘乘积A1A2A3A4A5A6,其中各矩阵的维数分别是:A1:30*35; A2:35*15; A3:15*5; A4:5*10; A5:10*20; A6:20*25递推关系:设计算A[i:j],1≤i≤j≤n,所需要的最少数乘次数m[i,j],则原问题的最优值为m[1,n]。
当i=j时,A[i:j]=A i,因此,m[i][i]=0,i=1,2,…,n当i<j时,若A[i:j]的最优次序在A k和A k+1之间断开,i<=k<j,则:m[i][j]=m[i][k]+m[k+1][j]+p i-1p k p j。
动态规划-(矩阵连乘)

12
4、构造最优解
void MatrixChain::Traceback(int i, int j) {
if(i==j) { cout<<'A'<<i; return;} if (i<s[i][j]) cout<<'('; Traceback(i, s[i][j]); if (i<s[i][j])cout<<')'; if(s[i][j]+1<j)cout<<'('; Traceback(s[i][j]+1, j); if(s[i][j]+1<j) cout<<')'; } void MatrixChain::Traceback() { cout<<'('; Traceback(0, n-1); cout<<')'; cout<<endl; }
②当i=j时,A[i:j]=Ai,因此,m[i][i]=0,i=1,2,…,n ③当i<j时,m [ i ] j ] [ m [ i ] k ] [ m [ k 1 ] j ] [ p i 1 p k p j
这里 A i 的维数为 pi1pi
∴可以递归地定义m[i][j]为:
m [i]j] [ m i k j{ m [i]n k [ ] m [k 0 1 ]j] [ p i 1 p kp j}i i j j
根据MatrixChain动态规划算法: ②计算m[i][j]数乘次数
m[2][5]=min m[2][2]+m[3][5]+p1p2p5=13000
矩阵连乘问题的算法

矩阵连乘问题的算法
一、矩阵连乘问题
矩阵连乘问题是指在矩阵计算中,给定n个矩阵,求这n个矩阵的连乘积的最优解问题。
矩阵连乘问题既可以用于组合优化,也可以用于信息处理系统中查找最优路径的搜索算法。
它是最基本的组合优化问题。
二、矩阵连乘问题的算法
1. 动态规划法:动态规划法是求解矩阵连乘问题的常用算法。
它采用递归方法,将原问题分解为若干个子问题,然后求出各子问题的最优解,最后组合出原问题的最优解。
2. 贪心算法:贪心算法是一种经典的最优化算法,也可以用于求解矩阵连乘问题,即通过某种启发式规则,在每一步中都使最优决策,最终得到最优解。
3. 分支定界法:分支定界法是一种由搜索算法和界定法相结合而成的最优化算法,也可以用于求解矩阵连乘问题。
该算法按照树状的层次结构,向下搜索一个在每一步骤都使得当前最优的路径,然后上溯形成最优解。
4. 模拟退火算法:模拟退火算法是一种搜索算法,它可以用于求解矩阵连乘问题。
它采用一种模拟物理过程的原理,通过不断地改变解的状态,以求出相对最优解。
- 1 -。
矩阵连乘问题(动态规划算法)

矩阵连乘问题(动态规划算法)动态规划算法思想简介:将⼀个问题分解为多个⼦问题,这点和分治法类似,但是每个⼦问题不是独⽴的⽽是相互联系的,所以我们在求解每个⼦问题的时候可能需要重复计算到其他的⼦问题,所以我们将计算过的⼦问题的解放进⼀个表中,这样就能避免了重复计算带来的耗费,这就是动态规划的基本思想;⼀般地,动态规划思想⼀般⽤来解最优化问题,主要分为以下四个步骤:(1)找出最优解的性质,并刻画其结构特征;(2)递归地定义最优值;(3)以⾃底向上的⽅式计算出最优值;(4)根据计算得到的最优值时得到的信息,构造最优解;同时,问题的最优⼦结构性质也是该问题可⽤动态规划算法求解的显著特征,这⾥的最优⼦结构性质即指:问题的最优解也即代表着它的⼦问题有了最优解;问题描述:分析过程如下:(1)分析最优⼦结构的性质:(2)分析递归关系,以及利⽤⾃底向上的⽅式进⾏计算:(3)获取最优值和最优解:代码如下:#ifndef MATRIX_CHAIN_H#define MATRIX_CHAIN_Hvoid matrix_chain(int *p, int n, int **m, int **s);void traceback(int i, int j, int **s);#endif#include <iostream>#include "matrix_chain.h"using namespace std;//利⽤动态规划算法获取最优值void matrix_chain(int *p, int n, int **m, int **s) //p:各个矩阵的列数,n:矩阵个数,m:m[i:j]矩阵i到j的相乘次数,s:对应的分开位置{for (int i = 0; i < n; i++){m[i][i] = 0;}for (int r = 2; r <= n; r++){for (int i = 0; i < n - r + 1; i++){int j = i + r - 1;m[i][j] = m[i + 1][j] + p[i - 1] * p[i] * p[j];s[i][j] = i;for (int k = i + 1; k < j; k++){int t = m[i][k] + m[k + 1][j] + p[i - 1] * p[k] * p[j];if (t < m[i][j]){m[i][j] = t;s[i][j] = k;}}}}}//利⽤s[i][j]获取最优解void traceback(int i, int j, int **s){if (i == j)return;traceback(i, s[i][j], s);traceback(s[i][j] + 1, j, s);cout << "Multiply A" << i << " , " << s[i][j];cout << "and A" << (s[i][j] + 1) << " , " << j << endl;}#include <iostream>#include "matrix_chain.h"using namespace std;int main(void){int matrix_num = 0; //矩阵个数cout << "请输⼊矩阵个数:" << endl;cin >> matrix_num;int **m = new int *[matrix_num];for (int i = 0; i < matrix_num; i++)m[i] = new int[matrix_num];int **s = new int *[matrix_num];for (int i = 0; i < matrix_num; i++)s[i] = new int[matrix_num];int *p = new int[matrix_num];cout << "请输⼊各矩阵的列数:" << endl;for (int i = 0; i < matrix_num; i++){cin >> p[i];}matrix_chain(p, matrix_num, m, s);traceback(0, matrix_num - 1, s);system("pause");return1;}可结合我的另⼀篇关于贪⼼算法的博客进⾏⽐较,了解这两者的区别;。
动态规划算法-装配线调度和矩阵链乘法

动态规划算法-装配线调度和矩阵链乘法⼀、动态规划之我见动态规划是算法中很常见的⼀种,这两天系统的⼜把其细看了下,有点⼼得,写下来做个备忘。
动态规划主要有⼏种类型:装配线调度(单次并⾏正向规划—⼀维,反向输出)矩阵链乘法(多次正向规划--⼀维,不同步长,正向输出)最长公共⼦序列(多次串⾏正向规划-⼆维,反向输出)最优⼆叉查找树(多次正向规划-⼀维,不同步长,正向输出)⼆、装配线调度简单的说,装配线调度是指在多条并⾏的流⽔线上(每个流⽔线能够进⾏转换)找到最快的输出结果的路径,是⼀个顺序的过程,所以只需要⼀次规划(循环)就能达到。
1.问题:⼀个车间,有N条流⽔线,每条流⽔线都有M个节点,每条流⽔线上的对应第m个节点的功能是相同的,且需要消耗不同的时间完成同⼀个功能,每个节点都能通过消耗⼀定的时间跳到另⼀条流⽔线的对应的下⼀个节点上,求在这N条流⽔线上,从第1个节点到第M个节点所需要的最短时间2.⼦问题拆分:既然求第1个节点到第M个节点所需要的最短时间,就需要每条流⽔线从第1个节点到第M个节点所需要的最短时间,进⼀步可以划分每条流⽔线上第1个节点到第M-1个节点所需要的最短时间,再加上该流⽔线上第M个节点所消耗的时间,就是该流⽔线上的第1个节点到第M个节点所需要的最短时间。
公式表达(以两条流⽔线为例):注:e1和e2表⽰进⼊1和2流⽔线的消耗, t2,j-1表⽰第2条流⽔线的第j-1个节点到第1条流⽔线的第j个节点的消耗。
f1[j-1]表⽰第1条流⽔线上第1个到第j-1个节点的最短时间。
3.伪代码FASTEST-WAY(a, t, e, x, n)f1[1] <- e1 + a[1, 1]f2[1] <- e2 + a[2, 1]for j <- 2 to nif f1[j - 1] + a[1, j] < f2[j - 1] + t[2, j - 1] + a[1, j]f1[j] = f1[j - 1] + a[1, j]l1[j] = 1elsef1[j] = f2[j - 1] + t[2, j - 1] + a[1, j]l1[j] = 2if f2[j - 1] + a[2, j] < f1[j - 1] + t[1, j - 1] + a[2, j]f2[j] = f2[j - 1] + a[2, j]l2[j] = 2elsef2[j] = f1[j - 1] + t[1, j - 1] + a[2, j]l2[j] = 1if f1[n] + x1 <= f2[n] + x2f* <- f1[n] + x1l* <- 1elsef* <- f2[n] + x2l* <- 24.代码#include <iostream.h>using namespace std;int Fastest_way(int *a, int *t, int *e, int *x, int n, int *l, int *f){int f1[n], f2[n];int ret = 0;f1[0] = e[0] + a[0 * n + 0];f2[0] = e[1] + a[1 * n + 0];for(int j = 1; j < n; j++){if(f1[j - 1] + a[0 * n + j] <= f2[j - 1] + t[1 * (n - 1) + j - 1] + a[0 * n + j]){f1[j] = f1[j - 1] + a[0 * n + j];l[0 * (n - 1) + j - 1] = 0;}else{f1[j] = f2[j - 1] + t[1 * (n - 1) + j - 1] + a[0 * n + j];l[0 * (n - 1) + j - 1] = 1;}if(f2[j - 1] + a[1 * n + j] <= f1[j - 1] + t[0 * (n - 1) + j - 1] + a[1 * n + j]){f2[j] = f2[j - 1] + a[1 * n + j];l[1 * (n -1) + j - 1] = 1;}else{f2[j] = f1[j - 1] + t[0 * (n - 1) + j - 1] + a[1 * n + j];l[1 * (n - 1) + j - 1] = 0;}}if(f1[n] + x[0] <= f2[n] + x[1]){*f = f1[n] + x[0];ret = 0;}else{*f = f2[n] + x[1];ret = 1;}return ret;}void Print_Stations(int *l, int num, int n){int i = num;cout<<"line "<<i + 1<<", station "<<n<<endl;for(int j = n - 2; j >= 0; j--){i = l[i * (n - 1) + j];cout<<"line "<<i + 1<<", station "<<(j - 1) % (n - 1) + 2<<endl;}}int main(int argc, char *argv[]){int a[2 * 6] = {7, 9, 3, 4, 8, 4, 8, 5, 6, 4, 5, 7};int t[2 * 5] = {2, 3, 1, 3, 4, 2, 1, 2, 2, 1};int l[2 * 5];int e[2] = {2, 4};int x[2] = {3, 2};int f = 0;int n = 6;int ret = Fastest_way(a, t, e, x, n, l, &f);Print_Stations(l, ret, n);return0;}三、矩阵链乘法矩阵链乘法是解决矩阵相乘问题的⼀个算法,⼤家都知道矩阵A(r*p)与B(p*q)相乘得到的矩阵C的维数将是r*q,乘法次数为r*p*q,如果多个矩阵相乘,其中相乘的顺序可以导致最终的相乘次数,相乘次数越少效率也就越⾼。
动态规划法解矩阵连乘问题

动态规划法解矩阵连乘问题实验内容给定n个矩阵{A1,A2,….An},其中Ai与Ai+1是可乘的,i=1,2,3。
,n-1。
我们要计算这n个矩阵的连乘积。
由于矩阵乘法满足结合性,故计算矩阵连乘积可以有许多不同的计算次序。
这种计算次序可以用加括号的方式确定。
若一个矩阵连乘积的计算次序完全确定,也就是说该连乘积已完全加括号,则我们可依此次序反复调用2个矩阵相乘的标准算法计算出矩阵连乘积。
解题思路将矩阵连乘积A(i)A(i+1)…A(j)简记为A[i:j],这里i <= j 。
考察计算A[i:j]的最优计算次序。
设这个计算次序在矩阵A(k)和A(k+1)之间将矩阵链断开,i <= k < j, 则其相应完全加括号方式为(A(i)A(i+1) …A(k)) * (A(k+1)A(k+2) …A(j))。
特征:计算A[i:j]的最优次序所包含的计算矩阵子链A[i:k]和A[k+1:j]的次序也是最优的。
矩阵连乘计算次序问题的最优解包含着其子问题的最优解。
设计算A[i:j] , 1 <= i <= j <= n ,所需要的最少数乘次数m[i,j],则原问题的最优值为m[1, n]当i = j 时,A[i:j]=Ai ,因此,m[i,i] = 0 , i = 1,2, …,n当i < j 时,m[i,j] = m[i,k] + m[k+1,j] + p(i-1)p(k)p(j) 这里A(i)的维数为p(i-1)*(i)( 注:p(i-1)为矩阵A(i)的行数,p(i)为矩阵A[i]的列数)实验实验代码#in elude <iostream>#in elude <vector>using n amespaee std ;class matrix_cha in{public:matrix_eha in(const vector <int> & c){ cols = c ;count = cols.size (); mc.resize (co unt);s.resize (co unt);for (i nt i = 0; i < count; ++i) { mc[i].resize (co unt); s[i].resize (co unt);}for (i = 0; i < count; ++i) { for (int j = 0; j < count; ++j) { mc[i][j] = 0 ;s[i][j] = 0 ;//记录每次子问题的结果void lookup_cha in () {__lookup_cha in (1, count - 1);min_count = mc[1][co unt - 1];cout << "min _multi_co unt = "<< min_count << endl ;//输出最优计算次序__trackback (1, count - 1);}//使用普通方法进行计算void calculate () {int n = count - 1; //矩阵的个数// r表示每次宽度// i,j表示从从矩阵i到矩阵j// k表示切割位置for (i nt r = 2; r <= n; ++ r) {for (int i = 1; i <= n - r + 1; ++ i) {int j = i + r - 1 ;//从矩阵i到矩阵j连乘,从i的位置切割,前半部分为0mc[i][j] = mc[i+1][j] + cols[i-1] * cols[i] * cols[j];s[i][j] = i ;for (int k = i + 1; k < j; ++ k) {int temp = mc[i][k] + mc[k + 1][j] +cols[i-1] * cols[k] * cols[j];if (temp < mc[i][j]) {mc[i][j] = temp ;s[i][j] = k ;}} // for k} // for i} // for rmin_count = mc[1][ n];cout << "min _multi_co unt = "<< min_count << endl ;//输出最优计算次序__trackback (1, n);private:int __lookup_cha in (int i, i nt j) {//该最优解已求出,直接返回if (mc[i][j] > 0) {return mc[i][j];}if (i == j) {return 0 ; //不需要计算,直接返回}//下面两行计算从i到j按照顺序计算的情况int u = __lookup_cha in (i, i) + __lookup_cha in (i + 1, j)+ cols[i-1] * cols[i] * cols[j];s[i][j] = i ;for (int k = i + 1; k < j; ++ k) {int temp = __lookup_cha in (i, k) + __lookup_cha in(k + 1, j)+ cols[i - 1] * cols[k] * cols[j];if (temp < u) {u = temp ;s[i][j] = k ;}}mc[i][j] = u ;return u ;}void __trackback (int i, i nt j) {if (i == j) {return ;}__trackback (i, s[i][j]);__trackback (s[i][j] + 1, j);cout <<i << "," << s[i][j] << " " << s[i][j] + 1 << "," << j << endl;}private:vector<int> cols ; // 列数int count ; // 矩阵个数+ 1vector<vector<int> > mc; //从第i个矩阵乘到第j个矩阵最小数乘次数vector<vector<int> > s; //最小数乘的切分位置int min_count ; //最小数乘次数};int mai n(){//初始化con st i nt MATRIX_COUNT = 6 ;vectorvi nt> c(MA TRIX_COUNT + 1);c[0] = 30 ;c[1] = 35 ;c[2] = 15 ;c[3] = 5 ;c[4] = 10 ;c[5] = 20 ;c[6] = 25 ;matrix_cha in me (c); // mc.calculate (); mc」o okup_cha in (); return 0 ;}实验结果实验验证从s 可知计算顺序为((A1(A2A3))((A4A5))A6))实验总结在这次实验中懂得了动态规划法运用方法和解题思路的重要性,在这个程序中如何 建立动态规划的过程建立递归过程 保存已解决的子问题答案。
动态规划矩阵链相乘

A2(A[3:4]): 计算A[3:4]的计算量+计算A2乘 (A[3:4])的计算量:240+10 ☓4 ☓10=640
A1 5☓10 A2 10☓4 A3 4☓6 A4 6☓10
50 ☓5 ☓ 100 +50 ☓100 ☓ 10 =75000 按A(BC)计算,所需乘法次数为:
5 ☓100 ☓ 10 + 50☓5 ☓10=7500 可见如何结合十分影响计算的效率,自然提 出了矩阵链相乘的最优计算次序问题
完全加括号的矩阵连乘积
完全加括号的矩阵连乘积可递归地定义为: (1)单个矩阵是完全加括号的;
两个矩阵相乘
若A是一个p*q矩阵,B是一个q*r矩阵,则 其乘积C=AB是一个p*r矩阵。
for(i=1;i<=p;i++) for(j=1;j<=r;j++) {c[i][j]=0; for(k=1;k<=q;k++)c[i][j]+=a[i][k]*b[k][j]; } 总共需要pqr次数乘。
(A1A2A3)乘A4的计算量:320+5 ☓6 ☓10=620
(A1A2)(A3A4): 200+240+5 ☓4 ☓10=640
A1(A2A3A4 ): 640 来存储 计算A[i:j]的最少数乘次数 例7.1:A1 5☓10 A2 10☓4 A3 4☓6 A4 6☓10 请给出计算A[1:4]的最优计算次序 1、计算规模为2的子问题 计算A[1:2] 所需乘法次数:5☓10 ☓4=200 计算A[2:3] 所需乘法次数:10☓4 ☓6=240 计算A[3:4]所需乘法次数: 4☓6☓10=240
矩阵链乘法(动态规划)

矩阵链乘法(动态规划)
⼀题意描述:
给定由n个要相乘的矩阵构成的序列(链)<A1,A2,A3,····A n>。
由于矩阵满⾜结合律(加括号⽅式表⽰结合⽅式),不同的计算⽅式导致的求出最终计算结果的代价相异,有的花的时间很少,有的⽅式所花时间很多,那么下⾯的任务就是求出算出结果所需要的最少时间及⼀个最优解。
⼆思路分析:
设p(n)表⽰⼀串n个矩阵可能的加全部括号⽅案数。
当n=1时,只有⼀个矩阵,此时p(1)=1。
当n>=2时,⼀个加全部括号的矩阵乘积等于两个加全部括号的⼦矩阵乘积的乘积,⽽且这两个⼦乘积之间的分裂可能发⽣在第k个和第k+1个矩阵之间,其中k=1,2,····,n-1;因此可以求得递归式:
1.找局部最优解:把问题:转化成两个最优⼦问题:及
2.构造递归解:
⾸先定义m[i,j]为解决⼦问题A[i....j]的最⼩计算次数,那么解决整个问题A[1,n]所花的最⼩时间为m[1,n]。
那么递归⽅程可以写成如下形式:
为了跟踪如何构造⼀个最优解我们可以定义s[i,j]为这样的⼀个k值,在该处分裂乘积后可得⼀个最优解。
3.构造函数进⾏求解
输出最优路径的函数⾃⼰编写,经过调⽤数组s[i][j]即可。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
问题描述:给定n个矩阵:A1,A2,...,A n,其中A i与A i+1是可乘的,i=1,2...,n-1。
确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少。
输入数据为矩阵个数和每个矩阵规模,输出结果为计算矩阵连乘积的计算次序和最少数乘次数。
问题解析:由于矩阵乘法满足结合律,故计算矩阵的连乘积可以有许多不同的计算次序。
这种计算次序可以用加括号的方式来确定。
若一个矩阵连乘积的计算次序完全确定,也就是说该连乘积已完全加括号,则可以依此次序反复调用2个矩阵相乘的标准算法计算出矩阵连乘积。
完全加括号的矩阵连乘积可递归地定义为:(1)单个矩阵是完全加括号的;(2)矩阵连乘积A是完全加括号的,则A可表示为2个完全加括号的矩阵连乘积B和C的乘积并加括号,即A=(BC)例如,矩阵连乘积A1A2A3A4有5种不同的完全加括号的方式:(A1(A2(A3A4))),(A1((A2A3)A4)),((A1A2)(A3A4)),((A1(A2A3))A4),(((A1A2)A3)A4)。
每一种完全加括号的方式对应于一个矩阵连乘积的计算次序,这决定着作乘积所需要的计算量。
看下面一个例子,计算三个矩阵连乘{A1,A2,A3};维数分别为10*100 , 100*5 , 5*50 按此顺序计算需要的次数((A1*A2)*A3):10X100X5+10X5X50=7500次,按此顺序计算需要的次数(A1*(A2*A3)):10*5*50+10*100*50=75000次所以问题是:如何确定运算顺序,可以使计算量达到最小化。
算法思路:例:设要计算矩阵连乘乘积A1A2A3A4A5A6,其中各矩阵的维数分别是:A1:30*35; A2:35*15; A3:15*5; A4:5*10; A5:10*20; A6:20*25递推关系:设计算A[i:j],1≤i≤j≤n,所需要的最少数乘次数m[i,j],则原问题的最优值为m[1,n]。
当i=j时,A[i:j]=A i,因此,m[i][i]=0,i=1,2,…,n当i<j时,若A[i:j]的最优次序在A k和A k+1之间断开,i<=k<j,则:m[i][j]=m[i][k]+m[k+1][j]+p i-1p k p j。
由于在计算是并不知道断开点k的位置,所以k 还未定。
不过k的位置只有j-i个可能。
因此,k是这j-i个位置使计算量达到最小的那个位置。
综上,有递推关系如下:构造最优解:若将对应m[i][j]的断开位置k记为s[i][j],在计算出最优值m[i][j]后,可递归地由s[i][j]构造出相应的最优解。
s[i][j]中的数表明,计算矩阵链A[i:j]的最佳方式应在矩阵A k和A k+1之间断开,即最优的加括号方式应为(A[i:k])(A[k+1:j)。
因此,从s[1][n]记录的信息可知计算A[1:n]的最优加括号方式为(A[1:s[1][n]])(A[s[1][n]+1:n]),进一步递推,A[1:s[1][n]]的最优加括号方式为(A[1:s[1][s[1][n]]])(A[s[1][s[1][n]]+1:s[1][s[1][n]]])。
同理可以确定A[s[1][n]+1:n]的最优加括号方式在s[s[1][n]+1][n]处断开...照此递推下去,最终可以确定A[1:n]的最优完全加括号方式,及构造出问题的一个最优解。
1、穷举法列举出所有可能的计算次序,并计算出每一种计算次序相应需要的数乘次数,从中找出一种数乘次数最少的计算次序。
对于n个矩阵的连乘积,设其不同的计算次序为P(n)。
每种加括号方式都可以分解为两个子矩阵的加括号问题:(A1...A k)(A k+1…A n)可以得到关于P(n)的递推式如下:以上递推关系说明,P(n)是随n的增长呈指数增长的。
因此,穷举法不是一个多项式时间复杂度算法。
2、重叠递归从以上递推关系和构造最优解思路出发,即可写出有子问题重叠性的递归代码实现://3d1-1 重叠子问题的递归最优解//A1 30*35 A2 35*15 A3 15*5 A4 5*10 A5 10*20 A6 20*25//p[0-6]={30,35,15,5,10,20,25}#include "stdafx.h"#include <iostream>using namespace std;const int L = 7;int RecurMatrixChain(int i,int j,int **s,int *p);//递归求最优解void Traceback(int i,int j,int **s);//构造最优解int main(){int p[L]={30,35,15,5,10,20,25};int **s = new int *[L];for(int i=0;i<L;i++){s[i] = new int[L];}cout<<"矩阵的最少计算次数为:"<<RecurMatrixChain(1,6,s,p)<<endl;cout<<"矩阵最优计算次序为:"<<endl;Traceback(1,6,s);return 0;}int RecurMatrixChain(int i,int j,int **s,int *p){if(i==j) return 0;int u = RecurMatrixChain(i,i,s,p)+RecurMatrixChain(i+1,j,s,p)+p[i-1]*p[i]*p[j];s[i][j] = i;for(int k=i+1; k<j; k++){int t = RecurMatrixChain(i,k,s,p) + RecurMatrixChain(k+1,j,s,p) + p[i-1]*p[k]*p[j];if(t<u){u=t;s[i][j]=k;}}return u;}void Traceback(int i,int j,int **s){if(i==j) return;Traceback(i,s[i][j],s);Traceback(s[i][j]+1,j,s);cout<<"Multiply A"<<i<<","<<s[i][j];cout<<" and A"<<(s[i][j]+1)<<","<<j<<endl;}1. 用算法RecurMatrixChain(1,4,s,p)计算a[1:4]的计算递归树如下图所示:2. 从上图可以看出很多子问题被重复运算。
可以证明,该算法的计算时间T(n)有指数下界。
设算法中判断语句和赋值语句为常数时间,则由算法的递归部分可得关于T(n)的递归不等式:3. 用数学归纳法可以证明,因此,算法RecurMatrixChain的计算时间也随n指数增长。
4. 3、备忘录递归算法5. 备忘录方法用表格保存已解决的子问题答案,在下次需要解决此子问题时,只要简单查看该子问题的解答,而不必重新计算。
备忘录方法为每一个子问题建立一个记录项,初始化时,该记录项存入一个特殊的值,表示该子问题尚未求解。
在求解的过程中,对每个带求的子问题,首先查看其相应的记录项。
若记录项中存储的是初始化时存入的特殊值,则表示该问题是第一次遇到,此时计算出该子问题的解,并将其保存在相应的记录项中,以备以后查看。
若记录项中存储的已不是初始化时存入的特殊值,则表示该子问题已被计算过,相应的记录项中存储的是该子问题的解答。
此时从记录项中取出该子问题的解答即可,而不必重新计算。
//3d1-2 矩阵连乘备忘录递归实现//A1 30*35 A2 35*15 A3 15*5 A4 5*10 A5 10*20 A6 20*25//p[0-6]={30,35,15,5,10,20,25}#include "stdafx.h"#include <iostream>using namespace std;const int L = 7;int LookupChain(int i,int j,int **m,int **s,int *p);int MemoizedMatrixChain(int n,int **m,int **s,int *p);void Traceback(int i,int j,int **s);//构造最优解int main(){int p[L]={30,35,15,5,10,20,25};int **s = new int *[L];int **m = new int *[L];for(int i=0;i<L;i++){s[i] = new int[L];m[i] = new int[L];}cout<<"矩阵的最少计算次数为:"<<MemoizedMatrixChain(6,m,s,p)<<endl;cout<<"矩阵最优计算次序为:"<<endl;Traceback(1,6,s);return 0;}int MemoizedMatrixChain(int n,int **m,int **s,int *p){for(int i=1; i<=n; i++){for(int j=1; j<=n; j++){m[i][j]=0;}}return LookupChain(1,n,m,s,p);}int LookupChain(int i,int j,int **m,int **s,int *p){if(m[i][j]>0){return m[i][j];}if(i==j){return 0;}int u = LookupChain(i,i,m,s,p) + LookupChain(i+1,j,m,s,p)+p[i-1]*p[i]*p[j];s[i][j]=i;for(int k=i+1; k<j; k++){int t = LookupChain(i,k,m,s,p) + LookupChain(k+1,j,m,s,p) + p[i-1]*p[k]*p[j];if(t<u){u=t;s[i][j] = k;}}m[i][j] = u;return u;}void Traceback(int i,int j,int **s){if(i==j) return;Traceback(i,s[i][j],s);Traceback(s[i][j]+1,j,s);cout<<"Multiply A"<<i<<","<<s[i][j];cout<<" and A"<<(s[i][j]+1)<<","<<j<<endl;}算法通过数组m记录子问题的最优值,m初始化为0,表明相应的子问题还没有被计算。