重玩动态规划二 LCS 最长公共子序列

第二部分了

重新理解动态规划:

1.解决问题对象:最优化问题

2.哪些可以处理:拥有最优子结构

3.优点:由于存在重叠子结构,在计算过程中记录这些子结构的解使算法复杂度大幅度降低(时间换空间,动态规划核心

4.算法步骤:

4.0 遇到一个问题,首先要进行分析(屁话但是有用的话),可以试探性的进行举例,对常例、特例进行分析,看看是不是一个最优化问题。

4.1 寻找最优子结构,将问题进行划分(例如按照问题的规模)

4.2 寻找划分之间的状态

4.3 编写状态转移函数

4.4 检查边界值

 

5.具体优化方面总结:

5.1 使用递归法解决子结构问题,并在计算时将递归结果存起来,在递归开始时如果找到存在的结果就直接返回结果不再深入进行递归(这样可以保证每一个子问题只被计算一次)。

5.2 5.1的方法比较简单,但是递归开销太大,可以通过最优子问题解之间的关系进行递推(可能是二维表),递推的方式效率很好。不过要思考最优子问题的递推关系。(如果递推关系复杂可考虑5.1)

5.3 动态规划算法的核心部分在于状态转移方程,利用这些转移过程,我们可以完成另外一些问题:

比如下面演示的最长公共子序列LCS算法,通过记录计算最长公共子序列长度的过程,记录LCS的构成次序。

敌强即屈,借花献佛

 

//最长公共子序列 LCS
/*
问题描述:

一个给定序列的子序列是在该序列中删去若干元素后得到的序列。确切地说,若给定序列X=<x1, x2,…, xm>,则另一序列Z=<z1, z2,…, zk>是X的子序列是
指存在一个严格递增的下标序列 <i1, i2,…, ik>,使得对于所有j=1,2,…,k
有
Xij=Zj
例如,序列Z=<B,C,D,B>是序列X=<A,B,C,B,D,A,B>的子序列,相应的递增下标序列为<2,3,5,7>。
给定两个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列。
例如,若X=<A, B, C, B, D, A, B>和Y=<B, D, C, A, B, A>,则序列<B, C, A>是X和Y的一个公共子序列,
序列<B, C, B, A>也是X和Y的一个公共子序列。而且,后者是X和Y的一个最长公共子序列,因为X和Y没有长度大于4的公共子序列。
最长公共子序列(LCS)问题:给定两个序列X=<x1, x2, …, xm>和Y=<y1, y2, … , yn>,要求找出X和Y的一个最长公共子序列。

*/


//#1问题分解
//只求最长公共子序列的长度
//这C[i][j]是X[0]--X[i]与Y[0]--Y[j]的最长公共子序列长度
/*
	c[i][j]=  c[i-1][j-1] xi==yi
	max(c[i][j-1],c[i-1][j])   xi!=yi
*/

//求C[i][j]与求编辑距离类似,
//由于C[i][j]的值可能与C[i-1][j-1],C[i-1][j],C[i][j-1]有关,所以整个二维表都要填值
//时间复杂度为O(m*n),
//空间复杂度为O(m*n)
int maxC(const char * a,const char *b){
	int i,j,k=0;
	//初始化计数空间
	int n = strlen(a); 
	int m = strlen(b);
	int **c = new int * [n+1];//c是从0计算到n,n+1位
	for(i=0;i<n+1;i++){
		c[i]=new int [m+1];
	}
	//初值
	for(j=1;j<n+1;j++){
		for(k=1;k<m+1;k++){
			c[j][k]=-1;//初始化
		}
	}
	//起始点
	for(k=0;k<m+1;k++){
		c[0][k]=0;
	}
	for(j=0;j<n+1;j++){
		c[j][0]=0;
	}
	//调用 二维数组推算
	for(j=1;j<n+1;j++){
		for(k=1;k<m+1;k++){
			if(a[j-1]==b[k-1]){
				c[j][k]=c[j-1][k-1]+1;
			}else{
				int x1 =c[j-1][k];
				int x2 =c[j][k-1];
				c[j][k]=x1>x2? x1:x2;
			}
		}
	}

	int des=c[n][m];


	
	//销毁内存

	for (i=0;i<n;i++) 
	{
		delete[] c[i];
	}                      
	delete[] c; 
	return des;
}


//###2 利用求解lcs的长度构建LCS,由于在建立C的时候考虑到了选择,那么用额外的O(m*n)空间,将这个选择记录
//建立完成C之后,伴随建立完成记录表,且知道LCS的长度,那么我们可以重新构造LCS
char * lcs(const char * x,const char *y){
	int i,j,k=0;
	//初始化计数空间
	int n = strlen(x); 
	int m = strlen(y);
	int **c = new int * [n+1];//c是从0计算到n,n+1位 //c是LCS的长度
	int **b = new int * [n+1];//b是c造成的原因
	for(i=0;i<n+1;i++){
		c[i]=new int [m+1];
		b[i]=new int [m+1];
	}
	//初值
	for(j=1;j<n+1;j++){
		for(k=1;k<m+1;k++){
			c[j][k]=-1;//初始化
		}
	}
	//起始点
	for(k=0;k<m+1;k++){
		c[0][k]=0;
		b[0][k]=-1;
	}
	for(j=0;j<n+1;j++){
		c[j][0]=0;
		b[k][0]=-1;
	}
	//调用 二维数组推算
	for(j=1;j<n+1;j++){
		for(k=1;k<m+1;k++){
			if(x[j-1]==y[k-1]){
				c[j][k]=c[j-1][k-1]+1;
				b[j][k]=0;               //状态0  斜上导致
			}else{
				int x1 =c[j-1][k];
				int x2 =c[j][k-1];
				if(x1>x2){
					c[j][k]=x1;
					b[j][k]=1;           //状态1  上面导致
				}else{
					c[j][k]=x2;
					b[j][k]=2;           //状态2  左边导致
				}
			
			}
		}
	}
	
	///
	//构造LCS
	//注意while的循环次数,每次至少使i或者j-1,即最多为i+j次,即m+n次,时间复杂度为O(m+n)
	//空间复杂度为O(min(m,n)),LCS最长不超过m,n最小值
	int length=c[n][m];
	char * lcs = new char[length+1]; //最后一位要标记'\0'
	int l=length;
	i=n;
	j=m;
	lcs[length]=0;
	while(i>0 || j>0){
		if(l<=0)break;//构造完成
		if(b[i][j]==0){
			lcs[l-1]=x[i-1];
			l--;
			i--;j--;
		}
		else if(b[i][j]==1){ //x[i-1][j]
			i--;
		}else{
			j--;
		}
	}
	
	

	
	//销毁内存

	for (i=0;i<n;i++) 
	{
		delete[] c[i];
	}                      
	delete[] c; 
	return lcs;
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值