动态规划——矩阵连乘的问题

动态规划算法与分页发类似,基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。

与分冶法不同的是,适合于用动态规划法求解的问题,经分解得到的子问题往往不是相互独立的。所以若用分冶法解这类问题,则分解得到的子问题太多,以至于最后解决原问题需要消耗指数时间。

在分冶法中,保存下了已解决的子问题的答案,在需要时再找出以求得的答案,就可以避免大量重复计算,从而得到多项式时间算法。

动态规划的步骤:

(1)找出最优解的性质,并刻画其结构特征

(2)递归地定义最优值

(3)以自底向上的方式计算出最优值

(4)根据计算最优值时得到的信息,构造最优解


给定n个矩阵,{ A1 , A2 , A3 , A4 ...... An } ,期中相邻的矩阵都是可以相乘的,由于矩阵乘法满足结合律,故存在不同的乘法顺序

比如对于矩阵 A1 , A2 , A3 , A4 , A5

可以有 (A1 (A2 (A3 (A4 A5))))   ,  (A1 (A2 (A3 A4) A5))) ... 等的组合乘法顺序


首先考虑两个矩阵乘积所需计算量,若A使p*q的矩阵,B是q*r的矩阵,则其乘积C=AB是一个p*r的矩阵,共需要pqr次数乘。


计算三个矩阵连乘{A1,A2,A3} 结舌维数分别为10*100 , 100*5 , 5*50

按此顺序计算需要的次数((A1*A2)*A3):10X100X5+10X5X50=7500次

按此顺序计算需要的次数(A1*(A2*A3)):10X5X50+10X100X50=75000次

所以问题是:如何确定运算顺序,可以使计算量达到最小化。


穷举搜索P(n)随n呈指数增长,下面考虑用动态规划法解:

1、分析最优解的结构

将AiAi+1...Aj记为A[i:j],考查计算A[1:n]的最优计算次序。设这个计算次序在矩阵Ak和Ak+1之间将矩阵链断开,1<=k<n,则其相应的完全加括号方式为((A1...Ak)(Ak+1...An))。它的总计算量为A[1:k]的计算量加上A[k+1:n]的计算量,再加上A[1:k]和A[k+1:n]相乘的计算量。

这个问题的一个关键特征是:计算A[1:n]的最优次序包含的计算矩阵子链A[1:k]和A[k+1:n]的次序也是最优的。

因此,矩阵连乘积计算次序问题的最优解包含其子问题的最优解。这种性质称为最优子结构性质。问题的最优子结构性质是该问题可用动态规划算法求解的显著特征。

2、建立递归关系

设计算A[i:j],1<=i<=j<=n所需的最少数乘次数为m[i][j],则原问题的最优值为m[1][n]。

当i=j时,A[i:j]为单一矩阵,无需计算,因此m[i][i]=0,i=1,2,...n。

当i<j时,可利用最优子结构计算m[i][j]=m[i][k]+m[k+1][j]+pi-1*pk*pj

若将对应于m[i][j]的断开位置k记为s[i][j],在计算出最优值m[i][j]后,可递归地由s[i][j]构造出相应的最优解。

3、计算最优值

用动态规划算法解此问题,可依据其递归式自底向上地计算,并保存已解决子问题的答案。


先计算出m[i][i] = 0,i=1,2,...n,然后根据递归式,按矩阵链长递增的方式依次计算m[i][i+1],i=1,2,...,n;m[i][i+2],i=...;...。易知,后边所用到的等号右边的数据都已经在前边被计算过

4、构造最优解

上算法只计算出了最优值,并未给出最优解,但已记录了构造最优解所需要的全部信息。s[i][j]中的数k表明计算矩阵链A[i:j]的最佳方式应在矩阵Ak和ak+1之间断开即最优哦的加括号方式应为(A[i:k])(A[k+1:j])。因此,从s[1][n]记录的信息可知计算A[1:n]的最优加括号方式。


#include<stdio.h>

void MatrixChain(int *p , int n , int m[][10] , int s[][10]) {
	int i , j , k , r , temp;
	// map[i][i]即为一个矩阵,所以赋值为0
	for(i=1 ; i<=n ; i++) {
		m[i][i] = 0;
	}
	// 从底向上,r代表此次每r个相邻矩阵相乘 
	for(r=2 ; r<=n ; r++) {
		// A[i:j],i表示第一个矩阵,j表示最后一个矩阵
		for(i=1 ; i<=n-r+1 ; i++) {
			j = i + r - 1;
			// k表示每次的断开点,从k= i...j-1
			k = i; 
			// k取值从i...j-1,由于开始map[i][j]为空,for循环需要对比
			// 所以把k=i第一次放在for外面
			m[i][j] = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j];
			// s记录每次的断开点
			s[i][j] = k;
			for(k=i+1 ; k<j ; k++) {
				// 对于每个k断开点,计算其计算量
				temp = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j];
				// 记录最小值和最小值时的断开点
				if(temp < m[i][j]) { 
					m[i][j] = temp;
					s[i][j] = k;
				}
			}
		}	
	}
}

// 计算A[i:j]的乘法顺序
void Traceback(int i , int j , int s[][10]) {
	if(i == j) {
		return;
	}
	// s[i][j]记录A[i:j]最佳方式的断开点,即( A[i,s[i][j]] )( A[s[i][j]+1][j])
	// 递归调用断开点的左侧和右侧
	printf("A%d...A%d 在k = %d处断开\n" , i , j , s[i][j]);
	Traceback(i , s[i][j] , s);
	Traceback(s[i][j]+1 , j , s);
}

int main() {
	int m[10][10] , s[10][10];
	int n = 5;
	// 这里假设n =5,
	// 矩阵的维数分别为30X35,35X15,15X5,5X10,10X20,20X25
	int p[10] = {30,35,15,5,10,20,25};
	MatrixChain(p,n,m,s);

	Traceback(1,n,s);
	printf("\n");
	return 0;
}

转载: http://blog.csdn.net/hb188753472/article/details/6672590




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值