动态规划之最长公共子序列(LCS)

算法课上,需要了解动态规划求lcs问题 ,便做个记录 :huaji21:
参考:动态规划解最长公共子序列(LCS)(附详细填表过程),【Python】最长公共子序列LCS

LCS的定义

最长公共子序列,即Longest Common Subsequence,LCS。
一个序列S任意删除若干个字符得到新序列T,则T叫做S的子序列;
两个序列X和Y的公共子序列中,长度最长的那个,定义为X和Y的最长公共子序列。
例如字符串acdfgadfc的最长公共子序列为adf
注意区别最长公共子串(Longest Common Substring)最长公共字串要求连续。

LCS的意义

求两个序列中最长的公共子序列算法,广泛的应用在图形相似处理、媒体流的相似比较、计算生物学方面、查重等。

蛮力法求解最长公共子序列:

需要遍历出所有的可能,时间复杂度是O(n³)

动态规划求解最长公共子序列:

分析规律:
X=<x1,x2,x3,x4...,xm>Y=<y1,y2,y3,y4...,yn>为两个序列,Z=<z1,z2,z3,z4...,zk>是他们的任意公共子序列

算法中的数据结构:长度数组

经过分析,我们可以知道:

1、如果xm = yn,则zk = xm = yn 且 Zk-1是Xm-1和Yn-1的lcs

2、如果xm != yn 且 zk != xm,则Z是Xm-1和Y的lcs

3、如果xm != yn 且 zk != yn,则Z是X和Yn-1的lcs

所以如果用一个二维数组c表示字符串X和Y中对应的前i,前j个字符的LCS的长度的话,可以得到以下公式:

当 i = 0, 或者j = 0 时 lcs 长度 为0;
当 i >0 且 j > 0 且 x[i] 等于y[j] 时 lcs长度为 c[i-1, j-1] + 1,及左上角值加1;
当 i >0 且 j > 0 且 x[i] 不等于 y[j] 时 lcs长度为 max( c[I, j - 1], c[i - 1,j]), 及上方和左方的最大值

算法中的数据结构:方向变量

使用二维数据B[m,n],其中,b[i,j]标记c[i,j]的值是由哪一个子问题的解达到的。即c[i,j]是由c[i-1,j-1]+1或者c[i-1,j]或者c[i,j-1]的哪一个得到的。取值范围为LeftTop ↖,Left ←,Top ↑ 三种情况

栗子

下面演示下c数组的填表过程:(以求ABCB和BDCA的LCS长度为例):

时间复杂度:

由于只需要填一个i行j列的二维数组,其中i代表第一个字符串长度,j代表第二个字符串长度
所以时间复杂度为O(i*j)

python 实现

def LCS(s1, s2):
    size1 = len(s1) + 1
    size2 = len(s2) + 1
    # 程序多加一行,一列,方便后面代码编写
    chess = [[["", 0] for j in list(range(size2))] for i in list(range(size1))]
    for i in list(range(1, size1)):
        chess[i][0][0] = s1[i - 1]
    for j in list(range(1, size2)):
        chess[0][j][0] = s2[j - 1]
    print("初始化数据:")
    print(chess)
    for i in list(range(1, size1)):
        for j in list(range(1, size2)):
            if s1[i - 1] == s2[j - 1]:
                chess[i][j] = ['↖', chess[i - 1][j - 1][1] + 1]
            elif chess[i][j - 1][1] > chess[i - 1][j][1]:
                chess[i][j] = ['←', chess[i][j - 1][1]]
            else:
                chess[i][j] = ['↑', chess[i - 1][j][1]]
    print("计算结果:")
    print(chess)
    i = size1 - 1
    j = size2 - 1
    s3 = []
    while i > 0 and j > 0:
        if chess[i][j][0] == '↖':
            s3.append(chess[i][0][0])
            i -= 1
            j -= 1
        if chess[i][j][0] == '←':
            j -= 1
        if chess[i][j][0] == '↑':
            i -= 1
    s3.reverse()
    print("最长公共子序列:%s" % ''.join(s3))
点赞

发表评论