Python中最长的公共子序列

时间:2018-02-06 21:00:57

标签: python algorithm dynamic-programming

我试图找到两个字符串之间最长的公共子序列。

我看了这个tutoial Python's wget

并写道:

# Longest Common Subsequence

def lcs(s1, s2):
    matrix = [ [0 for x in range(len(s2))] for x in range(len(s1)) ]
    cs = ""
    for i in range(len(s1)):
        for j in range(len(s2)):
            if s1[i]==s2[j]:
                if i==0 or j==0:
                    matrix[i][j] = 1
                    cs += s1[i]
                else:
                    matrix[i][j] = matrix[i-1][j-1] + 1
                    cs += s1[i]
            else:
                if i==0 or j==0:
                    matrix[i][j] = 0
                else:
                    matrix[i][j] = max(matrix[i-1][j], matrix[i][j-1])

    return matrix[len(s1)-1][len(s2)-1], cs


print(lcs("abcdaf", "acbcf"))  



I get (3, 'abccaf')

这显然是错误的应该是4 abcf。

不确定哪一步出错了。一个普遍的问题是程序员通常需要多长时间才能“获得”这类问题?

5 个答案:

答案 0 :(得分:3)

您的代码有两个主要问题导致算法输出错误的答案。

第16行

if i == 0 or j == 0

在视频后面显示此行在s1[1] != s2[j]时无效,因为“ab”和“a”的最长公共子序列的长度为1,尽管您的算法为此示例设置了matrix[0][1] = 0。所以你需要删除这个if语句。在你的时候,你必须考虑max(matrix[i-1][j], matrix[i][j-1])i == 0j == 0的作用。现在有两种不同的方法:

  1. 明确的一个:

    max(matrix[i-1][j] if i != 0 else 0, 
        matrix[i][j-1] if j != 0 else 0)
    
  2. 隐含的一个:

    max(matrix[i-1][j], matrix[i][j-1])
    

    这个有效,因为在Python中,负数索引用于获取列表的最后一项,在这种情况下这些项为0。

  3. 第11/14行

    cs += s1[i]

    例如,如果您发现“a”和“abcd”的最长公共子序列是“a”,则您的算法将“a”和“abcda”的最长公共子序列设置为“aa”,而不是合理。我正在努力解释为什么它不起作用,所以我建议你看几个例子,也许使用http://pythontutor.com/visualize.html

    解决方案

    要解决这两个问题,您可以使用矩阵存储您为较小问题找到的最长公共子序列。你最终得到了这个:

    def lcs(s1, s2):
        matrix = [["" for x in range(len(s2))] for x in range(len(s1))]
        for i in range(len(s1)):
            for j in range(len(s2)):
                if s1[i] == s2[j]:
                    if i == 0 or j == 0:
                        matrix[i][j] = s1[i]
                    else:
                        matrix[i][j] = matrix[i-1][j-1] + s1[i]
                else:
                    matrix[i][j] = max(matrix[i-1][j], matrix[i][j-1], key=len)
    
        cs = matrix[-1][-1]
    
        return len(cs), cs
    
    print(lcs("abcdaf", "acbcf"))  
    

    此特定实现仅返回一个可能的结果。您可以尝试实现一种算法,该算法将所有最长的常见序列作为练习。也许看看גלעדברקן

    建议的Wikipedia page

    “获取”代码无效的原因需要多长时间?

    显然没有明确的答案。考虑示例总是有帮助的,并且在算法的情况下,维基百科通常具有良好的伪代码,您可以将其实现为基础。当您熟悉算法中涉及的概念和数据结构时,您应该能够在一天之内实现它,我想说(但我绝对不是专家)。通常,在代码中搜索逻辑错误可能需要多天,具体取决于代码的大小。为了练习这种结构化,算法和数学思维,我强烈推荐projecteuler.net

答案 1 :(得分:1)

对于那些寻求内置解决方案的人:

from difflib import SequenceMatcher

str_a = "xBCDxFGxxxKLMx"
str_b = "aBCDeFGhijKLMn"
s = SequenceMatcher(None, str_a, str_b)

lcs = ''.join([str_a[block.a:(block.a + block.size)] for block in s.get_matching_blocks()])
# lcs = 'BCDFGKLM'

答案 2 :(得分:0)

上一个答案不是正确的解决方案(即使在大多数情况下也可以使用),正确的解决方案是创建矩阵,然后回溯矩阵 这是先前答案失败的情况 “ AAACCGTGAGTTATTCGTTCTAGAA” “ CACCCCTAAGGTACCTTTGGTTC”  最长的公共子序列应为“ ACCTAGTACTTTG”,但先前的代码未返回正确的result

(以下代码使用python 3)

enter image description here

def my_lcs(x_string, y_string):
    matrix = [[0 for each_x in range(0,len(y_string)+1)] for each_y in range(0,len(x_string)+1)]
    for each_y in range(len(y_string)):
        for each_x in range(len(x_string)):
            prev_x =each_x-1
            prev_y =each_y-1
            if(x_string[prev_x]== y_string[prev_y]):
                matrix[each_x][each_y] = matrix[prev_x][prev_y] + 1
            else:
                matrix[each_x][each_y] = max(matrix[prev_x][each_y] , matrix[each_x][prev_y])
    return matrix



#print /backtrack 
def print_lcs( mtrx, x_string, y_string ):
    result = []
    x, y = len( x_string ), len(y_string)
    while x> 0 and y > 0:
      if x_string[x- 1] == y_string[y - 1]:
          result.append( x_string[x- 1] )
          x-= 1
          y -= 1
      elif mtrx[x][y - 1] >= mtrx[x- 1][y]:
           y -= 1
      else:
          x-= 1
    print(result[::-1] )

inputa, inputb="1ab", "2ab" #ab
#inputa, inputb="AAACCGTGAGTTATTCGTTCTAGAA", "CACCCCTAAGGTACCTTTGGTTC" #ACCTAGTACTTTG 
#inputa, inputb="houseboat", "computer"#oue
#inputa, inputb="2193588", "21943588" #2193588
#inputa, inputb="ABCBDAB", "BDCABA" #BDAB
result= my_lcs(inputa, inputb)   
print_lcs(result,inputa, inputb)  

enter image description here

这是基于https://www.cs.fsu.edu/~burmeste/slideshow/chapter16/16-3.htmlhttp://www.columbia.edu/~cs2035/courses/csor4231.F11/lcs.pdf

答案 3 :(得分:0)

通过使用以下代码段获取LCS的长度,您会发现最大长度为14,因此BurningKarl的解决方案对我有用。

def longestCommonSequence(s1, s2, s1Index, s2Index, arr):
    if s1Index ==-1 or s2Index == -1:
        return 0
    if(arr[s1Index][s2Index] != None):
        return arr[s1Index][s2Index]

    if s1[s1Index] == s2 [s2Index]:
        result = 1+ longestCommonSequence(s1, s2, s1Index -1, s2Index -1, arr)
    else:
        result= max(longestCommonSequence(s1, s2, s1Index -1, s2Index, arr), longestCommonSequence(s1, s2, s1Index, s2Index -1, arr))
    arr[s1Index][s2Index] = result
    return result   

s1="AAACCGTGAGTTATTCGTTCTAGAA" 
s2="CACCCCTAAGGTACCTTTGGTTC" 

a = [[None for i in range(len(s2))] for j in range(len(s1))]
print(longestCommonSequence(s1, s2, len(s1)-1, len(s2)-1, a))

答案 4 :(得分:0)

def subrange(i, strr) :
    a = i[len(i) - 1]
    for j in range(len(i) - 1, len(strr)) :
       if a == strr[j] :
          return j
    return 
def subseq(strr) :
    prev = [i for i in strr]
    nex1 = []
    nex = []
    a = set()
    for i in prev :
       a.add(i)
    while( len(prev[0])< len(strr)):
       for i in prev :
           for j in range(subrange(i,strr) + 1, len(strr)) :
              nex.append(i + strr[j])
       prev =  nex
       nex = []
       for i in prev :
           a.add(i)
    return a
a1 = []
a2 = []
sol = ""
maxx = 0
str1 = input()
str2 = "sai"
if(len(str1) == 0 or len(str2) == 0) :
   sol = "NULL"
else :
   a1 = list(subseq(str1))
   a2 = list(subseq(str2))
   for i in range(0, len(a1)) :
       for j in range(0, len(a2)) :
          if a1[i] == a2[j] :
             if len(a1[i]) > maxx :
                sol = a1[i]
             maxx = len(sol)
print(sol)