> 文章列表 > 最长公共子序列长度问题的原理与实现

最长公共子序列长度问题的原理与实现

最长公共子序列长度问题的原理与实现

最长公共子序列(LCS)是一个在计算机科学和生物信息学中常见的问题,它指的是给定两个或多个序列,找出它们共同拥有的最长的子序列。子序列是指从原序列中删除一些元素(也可以不删除)后得到的新序列,不要求元素在原序列中连续。

例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。

最长公共子序列问题有许多实际的应用场景,比如比较文件的差异,比如Git用来调和文件之间的改变;比如比较DNA或蛋白质序列的相似度,比如在生物信息学中用来分析基因或进化关系。

那么,如何高效地求解最长公共子序列问题呢?一个常用的方法是动态规划算法,它可以在多项式时间内解决这个问题,而不需要穷举所有可能的子序列。

动态规划算法的基本思想是利用一个二维数组来存储两个序列之间的最长公共子序列的长度,然后根据递推公式来更新数组的值,最后回溯数组来找出最长公共子序列。

具体来说,假设我们有两个序列 X = x1x2…xm 和 Y = y1y2…yn ,我们定义一个二维数组 C[i,j] 表示 X 的前 i 个元素和 Y 的前 j 个元素之间的最长公共子序列的长度,则我们有以下递推公式:

  • 如果 i = 0 或 j = 0 ,则 C[i,j] = 0 ,因为空序列没有公共子序列。
  • 如果 i > 0 且 j > 0 ,则:
    • 如果 xi = yj ,则 C[i,j] = C[i-1,j-1] + 1 ,因为 xi 或 yj 可以作为最长公共子序列的最后一个元素。
    • 如果 xi != yj ,则 C[i,j] = max(C[i-1,j], C[i,j-1]) ,因为 xi 或 yj 不可能同时属于最长公共子序列。

根据这个递推公式,我们可以从左上角开始填充数组 C ,直到右下角得到 C[m,n] ,即为 X 和 Y 的最长公共子序列的长度。然后,我们可以从右下角开始回溯数组 C ,根据以下规则来找出最长公共子序列:

  • 如果 xi = yj ,则 xi 或 yj 是最长公共子序列的一个元素,将其加入结果中,并向左上方移动一格。
  • 如果 xi != yj ,则比较 C[i-1,j] 和 C[i,j-1] 的大小,向较大值所在的方向移动一格。
  • 如果 i = 0 或 j = 0 ,则停止回溯。

下面是一个例子,假设我们有两个序列 X = “ABCBDAB” 和 Y = “BDCABA” ,我们可以按照上述方法求出它们的最长公共子序列长度和内容:

下面是用 Python 语言实现的动态规划算法的代码:

def LCSLength(X, Y):# 初始化二维数组 Cm = len(X)n = len(Y)C = [[0] * (n + 1) for _ in range(m + 1)]# 填充数组 Cfor i in range(1, m + 1):for j in range(1, n + 1):if X[i - 1] == Y[j - 1]:C[i][j] = C[i - 1][j - 1] + 1else:C[i][j] = max(C[i - 1][j], C[i][j - 1])# 返回最长公共子序列的长度return C[m][n]def LCS(X, Y):# 获取二维数组 Cm = len(X)n = len(Y)C = LCSLength(X, Y)# 初始化结果字符串result = ""# 回溯数组 Ci = mj = nwhile i > 0 and j > 0:if X[i - 1] == Y[j - 1]:result = X[i - 1] + result # 将匹配的元素加入结果中i -= 1j -= 1else:if C[i - 1][j] > C[i][j - 1]:i -= 1else:j -= 1# 返回最长公共子序列return result# 测试
X = "ABCBDAB"
Y = "BDCABA"
print(LCSLength(X, Y)) # 输出 4
print(LCS(X, Y)) # 输出 BCBA 或 BDAB

其中,还有更加容易理解的递归版本(未做优化如记忆化搜索):

# 返回最长公共子序列
def process(s1,s2,i,j):if i==0 and j==0:return 1 if s1[i]==s2[j] else 0if i==0:if s2[j]==s1[i]:return 1else:return process(s1,s2,i,j-1)if j==0:if s1[i]==s2[j]:return 1else:return process(s1,s2,i-1,j)p1=process(s1,s2,i-1,j)p2=process(s1,s2,i,j-1)#之所以等于0是因为当第i,j位不相等时,答案一定包含在p1,p2情况中p3=process(s1,s2,i-1,j-1)+1 if s1[i]==s2[j] else 0return max(p1,p2,p3)def longestCommen(s1,s2):if s1==None or s2==None or len(s1)==0 or len(s2)==0:return 0s1=list(s1)s2=list(s2)# 0到len-1最长公共子序列长度return process(s1,s2,len(s1)-1,len(s2)-1)s1='abcdefghijklmn'
s2='abcdln'
print(longestCommen(s1,s2))