一个给定序列的子序列是在该序列中删去若干元素后得到的序列。如果序列Z既是序列X的子序列又是序列Y的子序列,则称Z是X和Y的公共子序列。
设序列,则 X m = x 1 , x 2 , … , x m , Y n = y 1 , y 2 , … , y n , Z k = z 1 , z 2 , … , z k X_m={x_1, x_2, …, x_m}, Y_n={y_1, y_2, …, y_n}, Z_k={z_1, z_2, …, z_k} Xm=x1,x2,…,xm,Yn=y1,y2,…,yn,Zk=z1,z2,…,zk, 最长公共子序列分如下几种情况:
在分析上述最长公共子序列的情况中,当
x
m
=
y
n
x_m = y_n
xm=yn时,有一个子问题,即 :找出
x
m
−
1
=
y
n
−
1
x_{m-1} = y_{n-1}
xm−1=yn−1的最长公共子序列;当
x
m
≠
y
n
x_m \neq y_n
xm=yn时,有两个子问题:即找出
x
m
−
1
和
y
x_{m-1} 和 y
xm−1和y的最长公共子序列, 和
x
和
y
n
−
1
x 和 y_{n-1}
x和yn−1的的最长公共子序列,这两个子问题都包含了同一个子问题
x
m
−
1
和
y
n
−
1
x_{m-1} 和y_{n-1}
xm−1和yn−1。 因此,本问题满足子问题重叠性质。
进一步:
令
c
[
i
]
[
j
]
c[i][j]
c[i][j]记录
X
i
和
Y
j
X_i和Y_j
Xi和Yj的最长公共子序列的长度,则有:
c
[
i
]
[
j
]
=
{
0
,
i
=
0
,
j
=
0
c
[
i
−
1
]
[
j
−
1
]
+
1
,
x
i
=
x
j
max
{
c
[
i
]
[
j
−
1
]
,
c
[
i
−
1
]
[
j
]
}
,
x
i
≠
x
j
c[i][j] = \begin{cases} 0, & \text i=0,j=0 \\ c[i-1][j-1] + 1, & \text x_i = x_j \\ \max\{c[i][j - 1], c[i - 1][j]\} , & \text x_i \neq x_j \end{cases}
c[i][j]=⎩
⎨
⎧0,c[i−1][j−1]+1,max{c[i][j−1],c[i−1][j]},i=0,j=0xi=xjxi=xj
例1:
X
4
=
A
,
C
,
B
,
D
;
Y
4
=
A
,
B
,
D
,
A
X_4 = A,C, B,D; Y_4 = A, B, D, A
X4=A,C,B,D;Y4=A,B,D,A
过程:
b矩阵:
0 表示没有移动。
1 表示向上移动。
2 表示向左移动。
3 表示斜左上移动。
LCS回溯:
从表格的右下角开始,检查当前位置对应的字符是否相等,若相等则左上移动;若不相等比较上面和左边两个单元格,选择较大的方向移动。继续上面过程直到c[0][0]
c矩阵:
| 0 | 0(A) | 0(B) | 0(D) | 0(A) |
|---|---|---|---|---|
| 0(A) | 1 | 1 | 1 | 1 |
| 0(C) | 1↑ | 1 | 1 | 1 |
| 0(B) | 1 | 2↖ | 2 | 2 |
| 0(D) | 1 | 2 | 3↖ | 3← |
b矩阵:
| 0 | 0 | 0 | 0 | 0 |
|---|---|---|---|---|
| 0 | 1 | 2 | 2 | 1 |
| 0 | 3 | 2 | 2 | 2 |
| 0 | 3 | 1 | 2 | 2 |
| 0 | 3 | 3 | 1 | 2 |
例2:
X
4
=
B
,
C
,
A
,
D
;
Y
4
=
C
,
A
,
B
,
D
X_4 = B,C, A,D; Y_4 = C, A, B, D
X4=B,C,A,D;Y4=C,A,B,D
过程:
c矩阵:
| 0 | 0 | 0 | 0 | 0 |
|---|---|---|---|---|
| 0 | 0 | 0 | 1 | 1 |
| 0 | 1 | 1 | 1 | 1 |
| 0 | 1 | 2 | 2 | 2 |
| 0 | 1 | 2 | 2 | 3 |
b矩阵:
| 0 | 0 | 0 | 0 | 0 |
|---|---|---|---|---|
| 0 | 2 | 2 | 1 | 3 |
| 0 | 1 | 3 | 2 | 2 |
| 0 | 2 | 1 | 3 | 3 |
| 0 | 2 | 2 | 2 | 1 |
def lcs_subsequence(x, y):
m = len(x)
n = len(y)
# 创建一个二维表格(m+1)*(n+1),初始值都为0;存储LCS长度即c[i][j]
dp = [[0] * (n + 1) for _ in range(m + 1)]
# 采用动态规划算法(相关的公式)填充表格
for i in range(1, m+1):
for j in range(1, n+1):
if x[i-1] == y[j-1]:
dp[i][j] = dp[i - 1][j - 1] + 1
else:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
# 输出LCS的长度
lcs_length = dp[m][n]
print("最长公共子序列 (LCS) 的长度:", lcs_length)
# 回溯构建LCS
lcs = []
i, j = m, n
while i > 0 and j > 0:
if x[i - 1] == y[j - 1]:
lcs.append(x[i - 1])
i -= 1
j -= 1
elif dp[i - 1][j] > dp[i][j - 1]:
i -= 1
else:
j -= 1
lcs.reverse()
return lcs
if __name__ == "__main__":
x = "ACBD"
y = "ABDA"
result = lcs_subsequence(x, y)
print("最长公共子序列 (LCS):", "".join(result))
运行结果:
