寻找两个序列的最长公共子序列
参考链接:https://www.geeksforgeeks.org/longest-common-subsequence-dp-4/
子序列特点:子序列中的元素与原序列中的元素以相同的相对顺序出现,子序列中的元素不一定连续出现在原序列中。
例如:“ABCDGH"和"AEDFHR"的最长公共子序列为"ADH”,其长度为3。“AGGTAB"和"GXTXAYB"的最长公共子序列为"GTAB”,其长度为4。
下面说明具体求解过程。
最优解的结构:
假设输入的长度为
n
n
n和
m
m
m的序列分别为
X
[
0
,
…
,
n
−
1
]
X[0,\dots,n-1]
X[0,…,n−1]和
Y
[
0
,
…
,
m
−
1
]
Y[0,\dots,m-1]
Y[0,…,m−1]。记
X
X
X和
Y
Y
Y的最长公共子序列的长度为
L
(
X
[
0
,
…
,
n
−
1
]
,
Y
[
0
,
…
,
m
−
1
]
)
L(X[0,\dots,n-1],Y[0,\dots,m-1])
L(X[0,…,n−1],Y[0,…,m−1])。
- 若
X
X
X和
Y
Y
Y的最后一个字符匹配,即
X
[
n
−
1
]
=
Y
[
m
−
1
]
X[n-1]=Y[m-1]
X[n−1]=Y[m−1],则
L ( X [ 0 , … , n − 1 ] , Y [ 0 , … , m − 1 ] ) = 1 + L ( X [ 0 , … , n − 2 ] , Y [ 0 , … , m − 2 ] ) 。 L(X[0,\dots,n-1],Y[0,\dots,m-1]) \\= 1 + L(X[0,\dots,n-2],Y[0,\dots,m-2])。 L(X[0,…,n−1],Y[0,…,m−1])=1+L(X[0,…,n−2],Y[0,…,m−2])。 - 若
X
X
X和
Y
Y
Y的最后一个字符不匹配,即
X
[
n
−
1
]
≠
Y
[
m
−
1
]
X[n-1] \ne Y[m-1]
X[n−1]=Y[m−1],则
L ( X [ 0 , … , n − 1 ] , Y [ 0 , … , m − 1 ] ) = max { L ( X [ 0 , … , n − 1 ] , Y [ 0 , … , m − 2 ] ) , L ( X [ 0 , … , n − 2 ] , Y [ 0 , … , m − 1 ] ) } 。 L(X[0,\dots,n-1],Y[0,\dots,m-1])\\ = \max \left\{ L(X[0,\dots,n-1],Y[0,\dots,m-2]), L(X[0,\dots,n-2],Y[0,\dots,m-1]) \right\}。 L(X[0,…,n−1],Y[0,…,m−1])=max{L(X[0,…,n−1],Y[0,…,m−2]),L(X[0,…,n−2],Y[0,…,m−1])}。
递归实现方法:
利用上述的递归表达式,我们可以写出最长公共子序列问题的递归实现C代码:
int findmax(int a, int b){
return (a>=b) ? a : b;
}
int LCS(char *X,char *Y,int m, int n){
int L;
if ((m == 0) || (n == 0))
return 0;
if (X[m-1] == Y[n-1]){
L = LCS(X,Y,m-1,n-1) + 1;
}else{
L = findmax(LCS(X,Y,m-1,n),LCS(X,Y,m,n-1));
}
return L;
}
该方法的复杂度为 O ( 2 n ) O(2^n) O(2n),复杂度较高,原因是出现了重复计算的情况。
利用表格的实现方法
为了降低复杂度,我们利用表格记录已经计算的结果。假设
X
=
‘
‘
G
X
T
X
A
Y
B
"
X=``GXTXAYB"
X=‘‘GXTXAYB",
Y
=
‘
‘
A
G
G
T
A
B
"
Y=``AGGTAB"
Y=‘‘AGGTAB",其中
n
=
7
n=7
n=7,
m
=
6
m=6
m=6。构造一个
(
n
+
1
)
×
(
m
+
1
)
(n+1)\times(m+1)
(n+1)×(m+1)的表格
L
L
L如下。
j j j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | |
---|---|---|---|---|---|---|---|---|
i i i | ∅ \empty ∅ | A | G | G | T | A | B | |
0 | ∅ \empty ∅ | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | G | 0 | 0 | 1 \blue 1 1 | 1 \red 1 1 | 1 | 1 | 1 |
2 | X | 0 | 0 | 1 \blue 1 1 | 1 \red 1 1 | 1 | 1 | 1 |
3 | T | 0 | 0 | 1 | 1 | 2 \red 2 2 | 2 | 2 |
4 | X | 0 | 0 | 1 | 1 | 2 \red 2 2 | 2 | 2 |
5 | A | 0 | 1 | 1 | 1 | 2 | 3 \red 3 3 | 3 |
6 | Y | 0 | 1 | 1 | 1 | 2 | 3 \red 3 3 | 3 |
7 | B | 0 | 1 | 1 | 1 | 2 | 3 | 4 \red 4 4 |
实现C代码如下。
int LCS_table(char* X, char* Y,int m,int n){
int L[m+1][n+1];
int i,j;
for(i=0;i<=m;i++){
for(j=0;j<=n;j++){
if ((i==0)||(j==0)){
L[i][j] = 0;
}else{
if (X[i-1] == Y[j-1]){
L[i][j] = L[i-1][j-1] + 1;
}else{
L[i][j] = findmax(L[i-1][j],L[i][j-1]);
}
}
}
}
int k = L[m][n];
char lcsstr[N] = {'\0'};
i = m;
j = n;
while((i>0)&&(j>0)){
if(X[i-1] == Y[j-1]){
lcsstr[k-1] = X[i-1];
k--;
i--;
j--;
}else{
if(L[i-1][j] > L[i][j-1]){
i--;
}else{
j--;
}
}
}
printf("The longest common string is %s\n",lcsstr);
return L[m][n];
}
那么打印出来最长公共子序列呢?
首先,将子序列初始化为空字符串。然后,从表格的右下角元素 L [ i ] [ j ] , i = n , j = m L[i][j],i=n,j=m L[i][j],i=n,j=m出发,
- 若
X
[
i
]
=
Y
[
j
]
X[i] = Y[j]
X[i]=Y[j],则将当前字符合并到之前子序列的前面,执行
i--; j--;
- 若
X
[
[
i
]
≠
Y
[
j
]
X[[i] \ne Y[j]
X[[i]=Y[j]
- 如果
L
[
i
−
1
]
[
j
]
>
L
[
i
]
[
j
−
1
]
L[i-1][j] > L[i][j-1]
L[i−1][j]>L[i][j−1],执行
i--;
- 如果
L
[
i
−
1
]
[
j
]
≤
L
[
i
]
[
j
−
1
]
L[i-1][j] \le L[i][j-1]
L[i−1][j]≤L[i][j−1],执行
j--;
- 如果
L
[
i
−
1
]
[
j
]
>
L
[
i
]
[
j
−
1
]
L[i-1][j] > L[i][j-1]
L[i−1][j]>L[i][j−1],执行
C代码已在上一段给出。