最长公共子序列也称作最长公共子串,英文缩写是LCS(Longest Common Subsequence)。其定义是:一个序列S,如果分别是两个或多个已知序列的子序列,且是符合此条件的子序列中最长的,则称S为已知序列的最长公共子序列。
关于子序列的定义通常有两种方式,一种是对子序列没有连续的要求,其子序列的定义就是原序列中删除若干元素后得到的序列。另一种是对子序列有连续的要求,其子序列的定义是原序列中连续出现的若干个元素组成的序列。求解子序列是非连续的最长公共子序列问题是一个十分实用的问题,它可以描述两段文字之间的“相似度”,即它们的雷同程度,从而能够用来辨别抄袭
1.1 最优子结构定义与边界值
现在来分析一下本问题的最优子结构。首先定义问题,假设有字符串str1长度为m,字符串str2长度为n,可以把子问题描述为:求字符串str1<1..m>中从第1个到第i(i <= m)个字符组成的子串str1<1…i>和字符串str2<1..n>中从第1个到第j(j <= n)个字符组成的子串str2<1…j>的最长公共序列,子问题的最长公共序列可以描述为d[i,j] = { Z1,Z2, … Zk },其中Z1-Zk为当前子问题已经匹配到的最长公共子序列的字符。子问题定义以后,还要找出子问题的最优序列d[i,j]的递推关系。分析d[i,j]的递推关系要从str1[i]和str2[j]的关系入手,如果str1[i]和str2[j]相同,则d[i,j]就是d[i-1,j-1]的最长公共序列+1,Zk=str1[i]=str2[j];如果str1[i]和str2[j]不相同,则d[i,j]就是d[i-1,j]的最长公共序列和d[i,j-1]的最长公共序列中较大的那一个。
最后是确定d[i,j]的边界值,当字符串str1为空或字符串str2为空时,其最长公共子串应该是0,也就是说当i=0或j=0时,d[i,j]就是0。d[i,j]的完整递推关系如下:
d[i,j]=0; i=j=0
d[i.j] = d[i-1,j-1];str[i]==str[j]
d[i,j] = max(d[i][j-1],d[i-1][j]);str[i]!=str[j]
反求最长公共子序列
根据1.1得到的最优解子结构递推关系,依次计算i从到m,j从1到n的d[i,j]值,最后得到的d[m,n]就是最长公共子序列的长度。d[m,n]只是最长公共子序列的长度值,表示了两个字符串的相似程度,如果要获得最长公共子序列,就需要在计算出d[m,n]矩阵的值后分析每一步决策的结果,根据每一个最优决策逆向构造出最长公共子序列。为此需要在递推计算d[i,j]的过程中,需要同时记录下最优决策的过程,最优决策的过程用矩阵r表示,r[i,j]表示最长公共子序列的长度值d[i,j]的“递推来源”。根据前面整理的递推关系,如果r[i,j]的值是1,则表示d[i,j]的值由d[i-1,j-1] + 1递推得到;如果r[i,j]的值是2,则表示d[i,j]的值由d[i-1,j]递推得到;如果r[i,j]的值是3,则表示d[i,j]的值由d[i,j-1]递推得到。以字符串“abcdea”和“aebcda”为例,根据递推关系得到的d和r合并到一个矩阵中显示
逆向构造最长公共子串的过程从r[m,n]开始,如果r[i,j]=1,则表示两个字符串中的str1[i]和str2[j]相同,可以将str1[i]或str2[j]插入到当前构造的最长公共子序列中。如果r[i,j]≠1,则不改变当前构造的最长公共子序列,但是要根据r[i,j]的值是2还是3,调整r[i,j]倒推的下一个位置。以上述两个字符串构造出的d矩阵和r矩阵为例,逆向构造最长公共子序列的过程如下:r[6,6]=1表示当前公共子串lcs = <str1[6]>(str1[6]和<str2[6]>值一样),同时前一次最优决策来自于d[5,5]。r[5,5]=2表示前一次决策来自于d[4,5],此时r[4,5]=1,表示当前公共子串lcs = < str1[4],str1[6]>,同时前一次最优决策来自于d[3,4]。r[3,4]=1表示当前公共子串lcs = < str1[3],str1[4],str1[6]>,同时前一次最优决策来自于d[2,3]。r[2,3]=1表示当前公共子串lcs = < str1[2],str1[3],str1[4],str1[6]>,同时前一次最优决策来自于d[1,2]。r[1,2]=3表示前一次决策来自于d[1,1],此时r[1,1]=1,表示当前公共子串lcs = < str1[1],str1[2],str1[3],str1[4],str1[6]>。由于r[0,0]是边界,因此逆向构造过程结束,得到最长公共子串的最终结果是lcs = < str1[1],str1[2],str1[3],str1[4],str1[6]>,对应的字符串就是<abcda>。
#include <string.h>
#include <curses.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>
#include <time.h>
#define MAX_STRING_LEN 64
typedef struct tagDPLS
{
int d; /*鏈€浼樺喅绛栧€*/
int r; /*鍐崇瓥鏂瑰悜*/
}DPLS;
int InitializeDplcs(char *str1, char *str2, DPLS dp[MAX_STRING_LEN][MAX_STRING_LEN])
{
int i,j;
for(i = 1; i<= strlen(str1);i++){
dp[i][0].d = 0;
}
for(j = 1; j<= strlen(str2);j++){
dp[0][j].d = 0;
}
for(i = 1; i<=strlen(str1);i++){
for(j = 1; j<= strlen(str2);j++){
if(str1[i -1] == str2[j-1]){
dp[i][j].d = dp[i-1][j-1].d + 1;
dp[i][j].r = 1;
}
else{
if(dp[i-1][j].d >= dp[i][j-1].d){
dp[i][j].d = dp[i-1][j].d;
dp[i][j].r = 2;
}else{
dp[i][j].d = dp[i][j-1].d;
dp[i][j].r =3;
}
}
}
}
return dp[strlen(str1)][strlen(str2)].d;
}
void GetLcs(DPLS dp[MAX_STRING_LEN][MAX_STRING_LEN],int i,int j, char *str1,char *lcs)
{
if(i ==0 || j==0){
return;
}
if(dp[i][j].r == 1){
GetLcs(dp,i-1,j-1,str1,lcs);
sprintf(lcs, "%s%c",lcs,str1[i-1]);
}else{
if(dp[i][j].r == 2)
GetLcs(dp,i -1,j,str1,lcs);
else
GetLcs(dp,i,j-1,str1,lcs);
}
}
#include <string.h>
enum decreaseDir {
kInt = 0,
kLeft,
kUp,
kLeftUp,
};
void LCS_Print(int **LCS_direction,
char *pStr1,char *pStr2,
size_t row,size_t col)
{
if(pStr1 == NULL || pStr2 ==NULL)
return;
size_t length1 = strlen(pStr1);
size_t length2 = strlen(pStr2);
if(length1==0 || length2==0){
return;
}
if(LCS_direction[row][col] == kLeftUp){
if(row > 0 && col>0){
LCS_Print(LCS_direction,pStr1,pStr2,row-1,col -1);
printf("%c",pStr1[row]);
}
}else if(LCS_direction[row][col] == kLeft){
if( col>0){
LCS_Print(LCS_direction,pStr1,pStr2,row,col -1);
}
}else if(LCS_direction[row][col] == kUp){
if( row>0){
LCS_Print(LCS_direction,pStr1,pStr2,row-1,col);
}
}
}
int LCS(char *pStr1, char *pStr2)
{
if(!pStr1 || !pStr2){
return 1;
}
size_t length1 = strlen(pStr1);
size_t length2 = strlen(pStr2);
if(!length1 || !length2){
return 0;
}
size_t i,j;
int **LCS_length;
LCS_length = (int **)malloc(length1*sizeof(int));
for(i =0; i<length1;i++ ){
LCS_length[i] = (int *)malloc(length2*sizeof(int));
}
for(i=0; i<length1; ++i){
for(j=0; j<length2; ++j){
LCS_length[i][j] = 0;
}
}
int **LCS_direction;
LCS_direction = (int **)(new int[length1]);
for(i =0; i<length1;i++ ){
LCS_direction[i] = (int *)new int[length2] ;
}
for(i=0; i<length1; ++i){
for(j=0; j<length2; j++){
LCS_direction[i][j] = kInt;
}
}
for(i=0; i<length1; ++i){
for(j=0; j<length2; j++){
if(i == 0|| j==0){
if(pStr1 == pStr2){
LCS_length[i][j] = 1;
LCS_direction[i][j] = kLeftUp;
}else{
LCS_length[i][j] = 0;
}
}else if(pStr1[i] == pStr2[j]){
LCS_length[i][j] = LCS_length[i-1][j-1];
LCS_direction[i][j] = kLeftUp;
}else if(LCS_length[i-1][j] > LCS_length[i][j-1]){
LCS_length[i][j] = LCS_length[i-1][j];
LCS_direction[i][j] = kUp;
}else if(LCS_length[i-1][j] < LCS_length[i][j-1]){
LCS_length[i][j] = LCS_length[i][j-1];
LCS_direction[i][j] = kLeft;
}
}
}
LCS_Print(LCS_direction,pStr1,pStr2,length1-1,length2-1);
return LCS_length[length1 -1][length2-1];
}
int GetLongestString(char *strTmp1, char *strTmp2, char *strTmp3)
{
int i = strlen(strTmp1);
int j = strlen(strTmp2);
int k = strlen(strTmp3);
return i>j?i:((j>k)?j:k);
}
void RecursionLCS( char *str1, char *str2, char *lcs)
{
if(strlen(str1)== 0 || strlen(str2) == 0)
return;
if(str1[0]== str2[0])
{
lcs += str1[0];
RecursionLCS(++str1, ++str2, lcs);
}
else
{
char *strTmp1,*strTmp2,*strTmp3;
RecursionLCS(str1++, str2, strTmp1); //尝试删除str1
RecursionLCS(str1, str2++, strTmp2); //尝试删除str2
RecursionLCS(str1++, str2++, strTmp3); //尝试同时删除str1和str2
lcs += GetLongestString(strTmp1, strTmp2, strTmp3);
}
}
int main()
{
char str1[] = "cdea";
char str2[] = "bcda";
char lcs[20],lcs1[20];
DPLS dp[MAX_STRING_LEN][MAX_STRING_LEN];
int i, j ;
InitializeDplcs(str1,str2,dp) ;//静态的分配的动态规划实现
GetLcs(dp,strlen(str1),strlen(str2), str1,lcs);
printf("%s\n",lcs);
printf("=============\n");
LCS(str1,str2);//动态分配的动态规划法实现
//RecursionLCS(str1, str2, lcs1);//调试未通过,穷举法
printf("%s\n",lcs1);
}