原文地址 http://www.daileinote.com/computer/math/0e
最长公共子序列(Longest Common Subsequence)(LCS)是一个在一个序列集合中(通常为两个序列)用来查找所有序列中最长子序列的问题
这与查找 最长公共子串 的问题不同的地方是:子序列不需要在原序列中占用连续的位置 。
最长公共子序列问题是一个经典的计算机科学问题,也是数据比较程序,比如 Diff 工具,和生物信息学应用的基础。它也被广泛地应用在版本控制,比如Git用来调和文件之间的改变
比如 abc abg bdf aeg acefg 等都是 abcdefg 的子序列
ABCDGH 和 AEDFHR 最长公共子序列为 ADH,长度为 3
AGGTAB 和 GXTXAYB 最长公共子序列为 GTAB,长度为 4
算法过程:
以 AGGTAB 和 GXTXAYB 为例
先判断最后一个字符是否相同,相同则
L(AGGTAB,GXTXAYB) = 1 + L(AGGTA,GXTXAY)
接着判断最后一个字符是否相同,不相同则
L(AGGTA,GXTXAY) = MAX(L(AGGT,GXTXAY), L(AGGTA,GXTXA))
以此类推,直到
L(A, ) = 0
L( ,G) = 0
例子 - 1
#include <stdio.h>
#include <string.h>
#define dl_max(x,y) ((x) > (y) ? (x) : (y))
int lcs(char *s1, int n1, char *s2, int n2 )
{
if (n1 == 0 || n2 == 0)
return 0;
if (s1[n1-1] == s2[n2-1]) {
return 1 + lcs(s1,n1-1,s2,n2-1);
}
return dl_max(lcs(s1,n1-1,s2,n2), lcs(s1,n1,s2,n2-1));
}
int main()
{
char s1[] = "daileinote";
char s2[] = "adeoeb";
int n1 = strlen(s1);
int n2 = strlen(s2);
int len = lcs(s1,n1,s2,n2);
printf("lcs len: %d\n", len);
return 0;
}
上面的这种实现方法时间复杂度为O(2^n),而且当这两个字符串没有相同字符时出现最差情况, 有点像下面的树
lcs("AXYT", "AYZX")
/
lcs("AXY", "AYZX") lcs("AXYT", "AYZ")
/ \ / \
lcs("AX", "AYZX") lcs("AXY", "AYZ") lcs("AXY", "AYZ") lcs("AXYT", "AY")
可以看出 lcs(“AXY”, “AYZ”) 实现了2次,如果我们把这个数画完,那么会有更多子问题被重复解决。
下面是利用动态规划的表格法的去解决
例子 - 2
#include <stdio.h>
#include <string.h>
#define dl_max(x,y) ((x) > (y) ? (x) : (y))
int print_lcs(char *s1, int n1, char *s2, int n2, int L[n1+1][n2+2])
{
int index = L[n1][n2];
char lcs[index+1];
lcs[index--] = 0;
while (n1 > 0 && n2 > 0) {
if (s1[n1-1] == s2[n2-1]) {
lcs[index--] = s1[n1-1];
n1--;
n2--;
} else if (L[n1-1][n2] > L[n1][n2-1]) {
n1--;
} else
n2--;
}
printf("lcs: %s\n", lcs);
}
int lcs(char *s1, int n1, char *s2, int n2 )
{
int i,j;
int L[n1+1][n2+1];
for (i = 0; i <=n1; i++) {
for (j = 0; j <= n2; j++) {
if (i == 0 || j == 0)
L[i][j] = 0;
else if (s1[i-1] == s2[j-1])
L[i][j] = 1 + L[i-1][j-1];
else
L[i][j] = dl_max(L[i-1][j], L[i][j-1]);
}
}
//打印
int index = L[n1][n2];
char lcs[index+1];
lcs[index--] = 0;
int kinds = 1;
i = n1;
j = n2;
while (i > 0 && j > 0) {
if (s1[i-1] == s2[j-1]) {
lcs[index--] = s1[i-1];
i--;
j--;
} else if (L[i-1][j] > L[i][j-1]) {
i--;
} else if (L[i-1][j] < L[i][j-1]) {
j--;
} else {
//两个方向都可以,这里我们选择其中一种
i--;
}
}
printf("lcs: %s\n", lcs);
return L[n1][n2];
}
int main()
{
char s1[] = "daileinote";
char s2[] = "adeoeb";
int n1 = strlen(s1);
int n2 = strlen(s2);
int len = lcs(s1,n1,s2,n2);
printf("lcs len: %d\n", len);
return 0;
}
时间复杂度为 O(mn),比例子1好很多
原文地址 http://www.daileinote.com/computer/math/0e