问题描述:字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。令给定的字符序列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=bn-1,则zk-1=am-1=bn-1,且“z0,z1,…,zk-2”是“a0,a1,…,am-2”和“b0,b1,…,bn-2”的一个最长公共子序列;
(2) 如果am-1!=bn-1,则若zk-1!=am-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列;
(3) 如果am-1!=bn-1,则若zk-1!=bn-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列。
这样,在找A和B的公共子序列时,如有am-1=bn-1,则进一步解决一个子问题,找“a0,a1,…,am-2”和“b0,b1,…,bm-2”的一个最长公共子序列;如果am-1!=bn-1,则要解决两个子问题,找出“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列和找出“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列,再取两者中较长者作为A和B的最长公共子序列。
求解:
引进一个二维数组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]。
问题的递归式写成:
![recursive formula](https://p-blog.csdn.net/images/p_blog_csdn_net/hhygcy/EntryImages/20090302/lcs_1.PNG)
回溯输出最长公共子序列过程:
![flow](https://p-blog.csdn.net/images/p_blog_csdn_net/hhygcy/EntryImages/20090302/lcs_2.PNG)
算法分析:
由于每次调用至少向上或向左(或向上向左同时)移动一步,故最多调用(m + n)次就会遇到i = 0或j = 0的情况,此时开始返回。返回时与递归调用时方向相反,步数相同,故算法时间复杂度为Θ(m + n)。
一切没有code的分析都是耍流氓。。。 上code
void printLCS(string str1, string str2, vector >flag, int idx1, int idx2){ if(idx1 == 0 || idx2 == 0 ) return; if(flag[idx1][idx2] == 1) { printLCS(str1, str2,flag, idx1-1, idx2-1); cout << idx1 <<"\t"<< idx2 <<"\t"; cout << str1[idx1-1] <<"\t"<
>flag; vector
tmp; tmp.resize(len2+1); for(size_t i = 0; i<= len1; i++) flag.push_back(tmp); //memset(flag,0,sizeof(flag)); // 1: leftup; 2: left; 3: up for(size_t i = 0; i <= len1; i++) { f[i][0] = 0; } for(size_t i = 0; i <= len2; i++) { f[0][i] = 0; } for(size_t i = 1; i <= len1; i++) { for(size_t j = 1; j <= len2; j++) { if(str1[i-1] == str2[j-1]) { f[i][j] = f[i-1][j-1] + 1; flag[i][j] = 1; } else { f[i][j] = max(f[i][j-1], f[i-1][j]); if(f[i][j-1] > f[i-1][j]) flag[i][j] = 2; else flag[i][j] = 3; } } }#if 0 for(size_t i = 1; i <= len1; i++) { for(size_t j = 1; j <= len2; j++) { //cout << "f["<
<<"][" < <<"]" << f[j][i] <<"\n"; cout << f[i][j] <<"\t"; } cout << endl; } cout << endl; for(size_t i = 1; i <= len1; i++) { for(size_t j = 1; j <= len2; j++) { //cout << "f["< <<"][" < <<"]" << f[j][i] <<"\n"; cout << flag[i][j] <<"\t"; } cout << endl; }#endif printLCS(str1, str2, flag, len1, len2); return f[len1][len2];} 找两个字符串的最长公共子串,这个子串要求在原字符串中是连续的。其实这又是一个序贯决策问题,可以用动态规划来求解。我们采用一个二维矩阵来记录中间的结果。这个二维矩阵怎么构造呢?直接举个例子吧:"bab"和"caba"(当然我们现在一眼就可以看出来最长公共子串是"ba"或"ab")
/** 找出两个字符串的最长公共连续子串的长度** author :liuzhiwei ** data :2011-08-16**/ #include "stdio.h"#include "string.h"#include "stdlib.h"int longest_common_substring(char *str1, char *str2){ int i,j,k,len1,len2,max,x,y; len1 = strlen(str1); len2 = strlen(str2); int **c = new int*[len1+1]; for(i = 0; i < len1+1; i++) c[i] = new int[len2+1]; for(i = 0; i < len1+1; i++) c[i][0]=0; //第0列都初始化为0 for(j = 0; j < len2+1; j++) c[0][j]=0; //第0行都初始化为0 max = -1; for(i = 1 ; i < len1+1 ; i++) { for(j = 1; j < len2+1; j++) { if(str1[i-1]==str2[j-1]) //只需要跟左上方的c[i-1][j-1]比较就可以了 c[i][j]=c[i-1][j-1]+1; else //不连续的时候还要跟左边的c[i][j-1]、上边的c[i-1][j]值比较,这里不需要 c[i][j]=0; if(c[i][j]>max) { max=c[i][j]; x=i; y=j; } } } //输出公共子串 char s[1000]; k=max; i=x-1,j=y-1; s[k--]='\0'; while(i>=0 && j>=0) { if(str1[i]==str2[j]) { s[k--]=str1[i]; i--; j--; } else //只要有一个不相等,就说明相等的公共字符断了,不连续了 break; } printf("最长公共子串为:"); puts(s); for(i = 0; i < len1+1; i++) //释放动态申请的二维数组 delete[] c[i]; delete[] c; return max;}int main(void){ char str1[1000],str2[1000]; printf("请输入第一个字符串:"); gets(str1); printf("请输入第二个字符串:"); gets(str2); int len = longest_common_substring(str1, str2); printf("最长公共连续子串的长度为:%d\n",len); system("pause"); return 0;}