最长公共子序列问题
最长公共子序列和最长公共子串区别
最长公共子串(Longest Common Substring)与最长公共子序列(Longest Common Subsequence)的区别:
子串要求在原字符串中是连续的,而子序列则只需保持相对顺序一致,并不要求连续。
例如 X = {a, Q, 1, 1}; Y = {a, 1, 1, d, f}
那么,{a, 1, 1}是X和Y的最长公共子序列,但不是它们的最长公共字串。
最优子结构
思路
我们的目的是寻找两个顺序相对一致的序列
所以我们需要构建一个二维数组进行统计,记录遍历的时候他们相同的地方
设有这样的两个序列:
{A,L,C,H,E,M,I,S,T}
{A,L,G,O,R,I,T,H,M,S}
第一行的意思可以看作空和空.空和A。。。的最长子序列都为0
从第二行开始,如果相同就将左上角的值复制加一填入,所以这里就填上 1(0+1)
到第三个,元素不同的话就将这个位置的左和上元素比较,填入最大的,这里上是0左是1,所以填入1;
以此类推
到了第二行,第二行与第二列元素一样,就读取左上角元素的复制后加一填入,1加1填入2
之后都是以此类推,最后得到了
状态转移方程代码
通过以上的分解后,我们可以得到这样一个状态转移方程
if(a===b){ //如果他们相同 table[row][col] = table[row - 1][co1 - 1] + 1; //我们找到他斜上角的那个值进行加一之后赋值 } else { table[row][col] = Math. max(table[row][col - 1], table[row - 1][col]);//如果不相同,我们就填入元素上方和左边最大的元素 }
返回存储的序列信息
得到变化信息的数组后得出序列就很容易了
设置一个函数为lcs,对得到的变化信息进行处理
设储存每一次的信息,如果xi=yj则为1,另外两种情况为dis[][]中储存为2,3;如1代表两个都是一样的,2代表行是左,3代表列是上
如果读取到为1的值说明在这里进行了作左上角的变化,说明他是一样的
使用递归即可完成
void LCS(int i, int j,char *A,int dis[100][100]){ if (i == 0 || j == 0) return; if (dis[i][j] == 1){ LCS(i - 1, j - 1,A,dis); printf("%c ", A[i-1]); } else if (dis[i][j] == 3){ LCS(i - 1, j,A,dis); }else{ LCS(i, j - 1,A,dis); } }
最终代码实现
合并整理后的代码
#include<stdio.h> #include<string.h> char A[100] = "ABCBDAB"; char B[100] = "BDCABA"; int len[100][100];//最大子序列的生成矩阵 int dis[100][100]; //创建数组dis[][]来储存每一次的信息,如果xi=yj则为1,另外两种情况为dis[][]中储存为2,3;如1代表两个都是,2代表行是左,3代表列是上 void LCSLength(int n ,int m, char *x,char *y,int len[100][100],int dis[100][100]){ int i, j, k; for (i = 0; i <= n; i++) len[i][0] = 0;//让列为0 for (i = 0; i <= m; i++) len[0][i] = 0;//让第一行为0 for (i = 1; i <= n; i++){ for (j = 1; j <= m; j++){ if (x[i-1] == y[j-1]){ //若他们相等,则让他等于左上角的元素加1 len[i][j] = len[i - 1][j - 1] + 1; dis[i][j] = 1; //记录下来在这里是相等的,标记为1 }else if (len[i - 1][j] >= len[i][j - 1]){ //他们不相等的时候,取上和左元素最大的那个 len[i][j] = len[i - 1][j]; dis[i][j] = 3;//取上标记为3 }else{ len[i][j] = len[i][j - 1]; dis[i][j] = 2; //取左标记为2 } } } } void LCS(int i, int j,char *A,int dis[100][100]){ if (i == 0 || j == 0)//递归到第0行就返回 return; if (dis[i][j] == 1){ //如果变化值为1就找再前一个变化值 LCS(i - 1, j - 1,A,dis); printf("%c ", A[i-1]);//大于这个值,这里注意了,递归后,他是从最底层的递归开始打印,就是我们需要的顺序了 } else if (dis[i][j] == 3){ LCS(i - 1, j,A,dis);//变化值为3,说明了应该找上边的 }else{ LCS(i, j - 1,A,dis);//变化值为3,说明了应该找左边的 } } int main() { int n = strlen(A); int m = strlen(B); printf("%d %d\n", n, m);//输出两个序列的长度 LCSLength(n,m,A,B,len,dis); printf("最大子序列长度:%d ", len[n][m]); //最后的一个按直接的结果,他的值就是最长的序列数 int i,j; for (i = 0; i <= n; i++){ for (j = 0; j <= m; j++){ printf("%d ", dis[i][j]); } puts(""); } puts("子序列为:"); LCS(n,m,A,dis); }
得到的结果是
ENDING...