1. 程式人生 > >最長公共子串與子序列-動態規劃(Python)

最長公共子串與子序列-動態規劃(Python)

最長公共子串(The Longest Common Substring)

       LCS問題就是求兩個字串最長公共子串的問題。解法就是用一個矩陣來記錄兩個字串中所有位置的兩個字元之間的匹配情況,若是匹配則為左上方的值加1,否則為0。然後求出對角線最長的1的序列,其對應的位置就是最長匹配子串的位置。

def find_lcsubstr(s1, s2):
     #生成0矩陣,為方便後續計算,比字串長度多了一行一列
    m=[[0 for i in range(len(s2)+1)]  for j in range(len(s1)+1)]
    #最長匹配的長度
    mmax=0
    #最長匹配對應在s1中的最後一位
    p=0  
    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
    #返回最長子串及其長度                
    return s1[p-mmax:p],mmax,m

最長公共子序列 (The Longest Common Subsequence)

      子串要求字元必須是連續的,但是子序列就不是這樣。解法就是用動態迴歸的思想,一個矩陣記錄兩個字串中匹配情況,若是匹配則為左上方的值加1,否則為左方和上方的最大值。一個矩陣記錄轉移方向,然後根據轉移方向,回溯找到最長子序列。

def find_lcseque(s1, s2): 
     # 生成字串長度加1的0矩陣,m用來儲存對應位置匹配的結果
    m = [ [ 0 for x in range(len(s2)+1) ] for y in range(len(s1)+1) ] 
    # d用來記錄轉移方向
    d = [ [ None for x in range(len(s2)+1) ] for y in range(len(s1)+1) ] 
 
    for p1 in range(len(s1)): 
        for p2 in range(len(s2)): 
            if s1[p1] == s2[p2]:            #字元匹配成功,則該位置的值為左上方的值加1
                m[p1+1][p2+1] = m[p1][p2]+1
                d[p1+1][p2+1] = 'ok'          
            elif m[p1+1][p2] > m[p1][p2+1]:  #左值大於上值,則該位置的值為左值,並標記回溯時的方向
                m[p1+1][p2+1] = m[p1+1][p2] 
                d[p1+1][p2+1] = 'left'          
            else:                           #上值大於左值,則該位置的值為上值,並標記方向up
                m[p1+1][p2+1] = m[p1][p2+1]   
                d[p1+1][p2+1] = 'up'         
    (p1, p2) = (len(s1), len(s2)) 
    print numpy.array(d)
    s = [] 
    while m[p1][p2]:    #不為None時
        c = d[p1][p2]
        if c == 'ok':   #匹配成功,插入該字元,並向左上角找下一個
            s.append(s1[p1-1])
            p1-=1
            p2-=1 
        if c =='left':  #根據標記,向左找下一個
            p2 -= 1
        if c == 'up':   #根據標記,向上找下一個
            p1 -= 1
    s.reverse() 

    return ''.join(s)

圖解如下所示