最长公共子串和最长公共子序列

最长公共子串(Longest Common Substring)和最长公共子序列(Longest Common Subsequence)的区别:

子串(Substring)是串的一个连续的部分,子序列(Subsequence)则是从不改变序列的顺序,而从序列中去掉任意的元素而获得的新序列;

更简略地说,前者(子串)的字符的位置必须连续,后者(子序列)则不必。


对于最长公共子串问题,用一个矩阵来记录两个字符串中所有位置的两个字符之间的匹配情况,若是匹配则为1,否则为0。然后求出对角线最长的1序列,其对应的位置就是最长匹配子串的位置。

但是在0和1的矩阵中找最长的1对角线序列又要花去一定的时间。通过改进矩阵的生成方式和设置标记变量,可以省去这部分工作。当字符匹配的时候,我们并不是简单的给相应元素赋上1,而是赋上其左上角元素的值加一。我们用两个标记变量来标记矩阵中值最大的元素的位置,在矩阵生成的过程中来判断当前生成的元素的值是不是最大的,据此来改变标记变量的值,那么到矩阵完成的时候,最长匹配子串的位置和长度就已经出来了。


例子:abcdefghigh 和 qbdefgihstr

矩阵:

0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 1 0 0 0 0 0 0 0 0
0 0 0 0 2 0 0 0 0 0 0 0
0 0 0 0 0 3 0 0 0 0 0 0
0 0 0 0 0 0 4 0 0 0 0 0
0 0 0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 1 0 0 0 0
0 0 0 0 0 0 1 0 0 0 0 0
0 0 0 0 0 0 0 0 1 0 0 0

result: “defg”


对于最长公共子序列问题,同样可以用动态规划思想求解。用一个矩阵来记录两个字符串中所有位置的两个字符之间的匹配情况,若是匹配则值为左上元素+1,否则为上值和左值的最大值。然后回溯求出+1序列路径,其对应的位置就是最长匹配子序列。


例子:abcdefghigh 和 qbdefgihstr

矩阵:

0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0
0 0 1 1 1 1 1 1 1 1 1 1
0 0 1 1 1 1 1 1 1 1 1 1
0 0 1 2 2 2 2 2 2 2 2 2
0 0 1 2 3 3 3 3 3 3 3 3
0 0 1 2 3 4 4 4 4 4 4 4
0 0 1 2 3 4 5 5 5 5 5 5
0 0 1 2 3 4 5 5 6 6 6 6

0 0 1 2 3 4 5 6 6 6 6 6
0 0 1 2 3 4 5 6 6 6 6 6
0 0 1 2 3 4 5 6 7 7 7 7

result:bdefgih


def find_LCSubstring(s1, s2):
    m=[[0 for j in range(len(s2)+1)] for i in range(len(s1)+1)]  #生成0矩阵,为方便后续计算,比字符串长度多了一列  
    mmax=0   #最长匹配的长度  
    p=0  #最长匹配对应在s1中的最后一位  
    for i in range(len(s1)):  
        for j in range(len(s2)):  
            if s1[i]==s2[j]:  
                m[i+1][j+1]=m[i][j]+1  
                if m[i+1][j+1]>mmax:  
                    mmax=m[i+1][j+1]  
                    p=i+1
    display(m,len(s1),len(s2))
    return s1[p-mmax:p],mmax   #返回最长子串及其长度  

def find_LCSubsequence(s1, s2):
    m=[[0 for j in range(len(s2)+1)] for i in range(len(s1)+1)]  #生成0矩阵
    for i in range(len(s1)):
        for j in range(len(s2)):
            if s1[i]==s2[j]:  #字符匹配成功,则该位置的值为左上方的值加1
                m[i+1][j+1]=m[i][j]+1
            elif m[i+1][j]>m[i][j+1]:  #字符不匹配,左值大于上值,则该位置的值为左值
                m[i+1][j+1]=m[i+1][j]
            else:  #字符不匹配,上值大于左值,则该位置的值为上值
                m[i+1][j+1]=m[i][j+1]
    i = len(s1)
    j = len(s2)
    res = ""
    display(m,i,j)
    while i>0 and j>0:  #回溯寻找最长子序列
        if m[i][j]==m[i-1][j-1]+1 and m[i][j]==m[i][j-1]+1 and m[i][j]==m[i-1][j]+1:
            res=s1[i-1]+res
            i-=1
            j-=1
        elif m[i][j]==m[i-1][j-1]+1 and m[i][j]==m[i-1][j]:
            i-=1
        elif m[i][j]==m[i-1][j-1]+1 and m[i][j]==m[i][j-1] and m[i][j]==m[i-1][j]+1:
            j-=1
        else:
            i-=1
            j-=1
    return res  #返回最长子序列
   
def display(matrix,rows,cols):
    for i in matrix:
        i = [str(x) for x in i]
        i = ' '.join(i)
        print (i)
        
if __name__ == '__main__':
    str1 = input("string 1:")
    str2 = input("string 2:")
    print ("LongestCommonSubstring")
    print (find_LCSubstring(str1,str2))
    print ("LongestCommonSubsequence")
    print (find_LCSubsequence(str1,str2))





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值