码迷,mamicode.com
首页 > 编程语言 > 详细

算法设计与分析(二)动态规划

时间:2016-07-19 11:12:08      阅读:217      评论:0      收藏:0      [点我收藏+]

标签:

动态规划基本思想:将待求问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题解。与分治不同的是,适应动态规划的问题具有两个特征:1)最优子结构,即问题的最优解包含了子问题的最优解。2)子问题重叠性质,即在递归中,出现了重复的子问题求解。

步骤:

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

2)递归地定义最优值;

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

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

我的理解是动态规划其实是递归地反向,通过从求解最简单的情况并将解记录下来,在层层往上利用前面得到的解。这样达到了消解递归带来的空间和时间开销,避免子问题的重复计算。


1》矩阵连乘问题

问题描述:给定n个矩阵:A1,A2,...,An,其中Ai与Ai+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次

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

 思路:解决问题的源头在于你要注意到在连乘的过程中(任意相乘),最后是两个括号内的内容相乘,运算量是多少?左边的括号内的运算量+右边运算量+两个括号相乘运算量。最优解是把括号放在了最合适的地方。怎样才叫最合适呢?是不是两边括号内的运算量也要是最少最优的,如果最优解的子结构不包含子问题的最优解则必然存在一种相乘次序运算量比最优解少,矛盾。以公式来看即:

递推关系

      设计算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时,若A[i:j]的最优次序在A
k和Ak+1之间断开,i<=k<j,则:m[i][j]=m[i][k]+m[k+1][j]+pi-1pkpj。由于在计算是并不知道断开点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]的最佳方式应在矩阵Ak和Ak+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]的最优完全加括号方式,及构造出问题的一个最优解。

#define SIZE 10

//p记录矩阵的行列数,Ai的行列号是p[i-1],p[i],m[][]记录相乘运算量,s][]记录最优解的地方
void matrixMultiply(int p[],int m[][SIZE],int s[][SIZE]){
	int n=SIZE-1;	//p的大小
	
	for(int i=0;i<=n;i++) m[i][i]=0;	//单个矩阵无法相乘,运算量为0
	
	for(int r=2;r<=n;r++){
		for(int i=1;i<=n-r+1;n++){	//n-r+1是左边矩阵链的最后一个矩阵编号
			
			int j=i+r-1;	//矩阵链长度从2个开始,当i=n-r+1时,j=n,即最后一个矩阵
			m[i][j]=m[i+1][j]+p[i-1]*p[i]*p[j];//计算i到j矩阵链,每次只算两个括号的相乘,即两个矩阵相乘,从2个长度开始累加
			s[i][j]=i;	//一开始设断开点是第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];	//计算i到j矩阵链中以k号矩阵为断开点的运算量
			
				if(k<m[i][j]){
					m[i][j]=k;
					s[i][j]=k;
				}
			}
		}
	}
}

2》最长公共子序列

【问题】 求两字符序列的最长公共字符子序列

问题描述:字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。令给定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一个严格递增下标序列<i0,i1,…,ik-1>,使得对所有的j=0,1,…,k-1,有xij=yj。例如,X=“ABCBDAB”,Y=“BCDB”是X的一个子序列。


废话一点,很多时候我们拿到题目都太过于着急,不多加思考就直接敲代码,边写边改。缺点太明显了,效率不高,而且这时最先想到的往往是最费力的解法,磨刀不误砍材工。这个问题还是要考虑到整体的一个关系,有时需要把答案拿过来一起想这道题如果直接在两个序列中去思考是怎么也发现不了下列关系的。

思路:

考虑最长公共子序列问题如何分解成子问题,设A=“a0,a1,…,am-1”,B=“b0,b1,…,bm-1”,并Z=“z0,z1,…,zk-1”为它们的最长公共子序列。不难证明有以下性质:

1)假如am-1=bm-1,则zk-1=am-1=bm-1;且z-{zk-1}是A-{am-1}和B-{bm-1}的最长公共子序列

2)若am-1!=bm-1,则Z必然是A-{am-1}和B的最长公共子序列或者是A和B-{bm-1}的最长公共子序列。


求解:

引进一个二维数组c[][],用c[i][j]记录X[i]与Y[j] 的LCS 的长度,b[i][j]记录c[i][j]是通过哪一个子问题的值求得的,以决定搜索的方向。
我们是自底向上进行递推计算,那么在计算c[i,j]之前,c[i-1][j-1],c[i-1][j]与c[i][j-1]均已计算出来。此时我们根据X[i] = Y[j]还是X[i] != Y[j],就可以计算出c[i][j]。

问题的递归式写成:


技术分享

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define SIZE 100

int a[SIZE][SIZE];

void f(char x[],char y[],int b[][SIZE]){
	int xlen=strlen(x);
	int ylen=strlen(y);

	//初始化
	for(int i=0;i<xlen;i++)	a[i][0]=0;
	for(int j=0;j<ylen;j++)	a[0][j]=0;
	

	for(int i=1;i<xlen;i++){
		for(int j=1;j<ylen;j++){
			if(x[i]==x[j]){	//最后一个字母相等
				a[i][j]=a[i-1][j-1]+1;
				b[i][j]=1;
			}
			else if(c[i-1][j]>=c[i][j-1]){
				c[i][j]=c[i-1][j];
				b[i][j]=2;
			}else{
				c[i][j]=c[i][j-1];
				b[i][j]=3;
			}
		}
	}

}

//输出最优解
void LCS(int i,int j,char x[],int b[][SIZE]){
	if(i==0 || j==0)
		return ;
	if(b[i][j]==1){
		lcs(i-1,j-1,x,b);
		printf("%c",x[i]);
	}else if(b[i][j]==2){
		LCS(i-1,j,x,b);
		else
			LCS(i,j-1,x,b);
	}

}




算法设计与分析(二)动态规划

标签:

原文地址:http://blog.csdn.net/c340c340/article/details/48299381

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!