【问题标题】:Longest Common Subsequence in PythonPython中最长的公共子序列
【发布时间】:2018-07-17 00:57:32
【问题描述】:

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

我看了这个教程https://www.youtube.com/watch?v=NnD96abizww

并写道:

# 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。

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

【问题讨论】:

标签: python algorithm dynamic-programming


【解决方案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'

【讨论】:

  • 太棒了!在我的用例中就像一个魅力。
  • 注意:这实际上并不返回最长的公共子序列,而是返回一个“人类可读”的字符串差异。详情见文档:docs.python.org/3/library/difflib.html
  • 感谢您指出这一点,我不知道。如果我们在SequenceMatcher 实例上使用find_longest_match 会怎样,就像在这个例子中一样? --> tutorialspoint.com/…
【解决方案2】:

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

if i == 0 or j == 0 第 16 行

只要跟着视频显示,当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。

cs += s1[i] 11/14 行

例如,如果您发现“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

【讨论】:

  • 这段代码似乎对我产生了错误的答案。我已经用命名元组对其进行了测试。
  • @ikamen 你能为你的问题提供一个简短的例子吗?该代码假定 s1 和 s2 是字符串。不打算使用collections.namedtuples 作为参数,所以这段代码完全有可能被破坏。
  • 好吧,如果它不打算在元组上工作,那么那里没有什么可以修复的。我的测试只是通过了两个具有 5 个命名元组排列的列表,并与正确答案进行比较。
  • 如果输入字符串中有重复项,则并不总是有效:“abcadaf”和“acabcf”
  • @EL 究竟什么不起作用?我得到'abcf' 作为您示例的结果。那是长度为 4 的常见子序列,当然还有其他长度为 4 的子序列,但是你找到任何长度 > 4 的子序列了吗?
【解决方案3】:

通过使用以下代码段获取 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】:
    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)
    

    【讨论】:

      猜你喜欢
      • 2014-11-13
      • 2011-03-01
      • 1970-01-01
      • 2011-02-25
      • 2013-02-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多