动态规划之最长公共子序列

算法总体思想

 动态规划(Dynamic Programming)是通过组合子问题的解而解决整个问题的。分治是指将问题划分成一些独立的子问题,递归地求解各子问题,然后合并子问题的解而得到原始问题的解,与此不同,动态规划适用于子问题不是独立的情况,也就是各个子问题包含公共的子问题。在这种情况下,采用分治法会做许多不必要的工作,即重复地求解公共地子问题。动态规划算法对每个子问题只求解一次,将其结果保存在一张表中,从而避免每次遇到各个子问题时重新计算答案。

动态规划算法的基本要素


-、最优子结构


矩阵连乘计算次序问题的最优解包含着其子问题的最优解。这种性质称为最优子结构性质。
在分析问题的最优子结构性质时,所用的方法具有普遍性:首先假设由问题的最优解导出的子问题的解不是最优的,然后再设法说明在这个假设下可构造出比原问题最优解更好的解,从而导致矛盾。
利用问题的最优子结构性质,以自底向上的方式递归地从子问题的最优解逐步构造出整个问题的最优解。最优子结构是问题能用动态规划算法求解的前提。

注意:同一个问题可以有多种方式刻划它的最优子结构,有些表示方法的求解速度更快(空间占用小,问题的维度低)

二、重叠子问题


递归算法求解问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次。这种性质称为子问题的重叠性质。
动态规划算法,对每一个子问题只解一次,而后将其解保存在一个表格中,当再次需要解此子问题时,只是简单地用常数时间查看一下结果。
通常不同的子问题个数随问题的大小呈多项式增长。因此用动态规划算法只需要多项式时间,从而获得较高的解题效率。 

问题举例


最长公共子序列

•若给定序列X={x1,x2,…,xm},则另一序列Z={z1,z2,…,zk},是X的子序列是指存在一个严格递增下标序列{i1,i2,…,ik}使得对于所有j=1,2,…,k有:zj=xij。例如,序列Z={B,C,D,B}是序列X={A,B,C,B,D,A,B}的子序列,相应的递增下标序列为{2,3,5,7}。
•给定2个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列。
•给定2个序列X={x1,x2,…,xm}和Y={y1,y2,…,yn},找出X和Y的最长公共子序列。

最长公共子序列的结构

设序列X={x1,x2,…,xm}和Y={y1,y2,…,yn}的最长公共子序列为Z={z1,z2,…,zk} ,则

(1)若xm=yn,则zk=xm=yn,且zk-1是xm-1和yn-1的最长公共子序列。

(2)若xm≠yn且zk≠xm,则Z是xm-1和Y的最长公共子序列。

(3)若xm≠yn且zk≠yn,则Z是X和yn-1的最长公共子序列。

由此可见,2个序列的最长公共子序列包含了这2个序列的前缀的最长公共子序列。因此,最长公共子序列问题具有最优子结构性质。 


子问题的递归结构

由最长公共子序列问题的最优子结构性质建立子问题最优值的递归关系。用c[i][j]记录序列和的最长公共子序列的长度。其中,Xi={x1,x2,…,xi}Yj={y1,y2,…,yj}。当i=0j=0时,空序列是XiYj的最长公共子序列。故此时C[i][j]=0。其他情况下,由最优子结构性质可建立递归关系如下:

(上传好几次不成功)


计算最优值(伪代码)


AlgorithmlcsLength(x,y,b)

mßx.length-1;

nßy.length-1;

c[i][0]=0;c[0][i]=0;

for(inti= 1; i<= m;i++)

    for(intj = 1; j <= n; j++)

      if(x[i]==y[j])

         c[i][j]=c[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;


源代码实现(测试通过)


#include<iostream>
#define LEN_ARR_A  20
#define LEN_ARR_B  12
using namespace std;

/*
 *  brief     : calculate longest common substring of two string and 
 *              mark  path of getting the longest common substring  
 *	parameter : lenComStr : length of common substring
 *              solutionPath : mark of path of solution
 *
 *  note:      dynamic programming :caculate longest common substring between X(n)
 *              and Y(m-1),likey to caculate longest common substring between X(n) 
 *              and Y(m-1) or between X(n-1) and Y(m). X(0) and Y(0) are not referenced!
 *
 *
 *	return :    null
 */
template<class Type>
void LCSLength(size_t lenStrA, size_t lenStrB, Type *strA, Type *strB, size_t ** lenComStr, size_t ** solutionPath)
{
	for(size_t i = 0; i < lenStrA; ++i)
		lenComStr[i][0] = 0;
	for(size_t i = 0; i < lenStrB; ++i)
		lenComStr[0][i] = 0;
	for(size_t i = 1; i < lenStrA; ++i)
		for(size_t j = 1; j < lenStrB; ++j){
			if(strA[i] == strB[j]){
				lenComStr[i][j] = lenComStr[i -1][j - 1] + 1; 
				//global solution depends on local solution
				solutionPath[i][j] = 1;               
				//mark  path of getting the longest common substring 
			}
			else if(lenComStr[i - 1][j] >= lenComStr[i][j - 1]){
				lenComStr[i][j] = lenComStr[i - 1][j];  
				//global solution depends on local solution
				solutionPath[i][j] = 2;
			}else{
				lenComStr[i][j] = lenComStr[i][j - 1];  
				//global solution depends on local solution
				solutionPath[i][j] = 3;
			}
		}
}
/*
 *  brief     : output longest common substring of two string  
 *              
 *	parameter : i is beginning index of char array
 *              j is end index of char array
 *              solutionPath : mark of path of solution
 *
 *  note:      value of solutionPath[i][j] has 3 states  
 *
 *	return :    null
 */
 template<class Type>
void LCS(size_t i, size_t j, Type * strA, size_t ** solutionPath)
{
	if(i == -1 || j == -1)	return;
	if(solutionPath[i][j] == 1){
		LCS(i - 1, j - 1, strA, solutionPath); 
		cout << strA[i];
	}else if(solutionPath[i][j] == 2)
			LCS(i - 1, j, strA, solutionPath);
	else{
			LCS(i, j - 1, strA, solutionPath);
	}
}

int main()
{
	char strA[LEN_ARR_A] = { '0','b', 'c', 'e', 'f', 's', 'j', 'f', 'u', 'i', 'j', 'k', 'y', 'h', 'n', 't', 'r', 'd', 'e', 'm'};
	char strB[LEN_ARR_B] = { '0', 'e', 'j', 'u', 'k', 'y', 'n', 'r', 'd', 'e', 'f', 's'};

	size_t ** lenComStr = new size_t*[LEN_ARR_A];
	size_t ** solutionPath = new size_t*[LEN_ARR_A];
	for(size_t i = 0; i < LEN_ARR_A; ++i)	{
		lenComStr[i] = new size_t[LEN_ARR_B];
		solutionPath[i] = new size_t[LEN_ARR_B];
	}
	LCSLength(LEN_ARR_A, LEN_ARR_B, strA, strB, lenComStr, solutionPath);
	LCS(LEN_ARR_A - 1, LEN_ARR_B - 1, strA, solutionPath);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值