1. 程式人生 > >Python程式設計實現對2個字串最長的公共子串的多種求解方式,效能測試及優化

Python程式設計實現對2個字串最長的公共子串的多種求解方式,效能測試及優化

解法1-暴力求解法:

def LongestCommonSubstring(FirstString,SecondString):
    '''
    求最長子串解法1:
    以字串1的每個漢字作為起始位置
    去字串2中找到能與之匹配的最長長度
    將這個長度和記錄的最長長度比較,從而找到最長的子串長度
    然後通過字串2的起始位置和最長長度,找到這個子串
    
    FirstString----字串1
    SecondString---字串2
    Longest--------最長公共子串的長度
    ComputeTimes--計算次數,用於對比計算量
    '''
    FirstStringLenght = len(FirstString)
    SecondStringLenght = len(SecondString)
    Longest = 0
    ComputeTimes=0
    for i in range(FirstStringLenght):
        for j in range(SecondStringLenght):
            m,n,Longer = i,j,0
            while FirstString[m]==SecondString[n]:
                ComputeTimes+=1
                Longer += 1
                if m>FirstStringLenght | n>SecondStringLenght:break
                m,n = m+1,n+1
                if Longer>Longest:
                    ComputeTimes+=1
                    Longest,SecondStringStartPoint = Longer,j  
    
    return Longest,SecondString[SecondStringStartPoint:SecondStringStartPoint+Longest],ComputeTimes
  •  時間複雜度: O(n^3) ;
  • 空間複雜度:O(n^1)

輸出的時間和電腦的狀態有關,所以迴圈呼叫方法1000次,求平均。這樣的結果更加真實。

str1='怎麼理解一家漏水,四家遭殃的情況'

str2='''案件詳情,2015 年 11 月 4 日因廣粵支路 57 弄 11 號 503 室住戶吳根寶,因為灶頭間晚上水龍頭未關緊,
滴水了一晚上,導致 403/303/203/103 室四家均受到影響。當日四家住戶當即找到漏水根源的 503 室,但由於家中無人,敲門無人迴應,
真是一家漏水,四家遭殃啊。'''
In [19]:import time
    ...:start=time.clock()
    ...:for i in range(1000):
    ...:    LongestSubstring=LongestCommonSubstring(str1,str2)
    ...:print((time.clock()-start)/1000)
    ...:print(LongestSubstring)

Out[19]: 0.000311501507480898
    ...: (9, '一家漏水,四家遭殃', 80)
       

結果: 

  • 耗時: 0.00031173843790838874
  • 計算次數:80

解法2-動態規劃求解法:

import numpy as np
def LongestCommonSubstring(FirstString,SecondString):
    '''
    求最長子串解法2:
    建立一個以字串1長度+1乘字串2長度+1的矩陣
    矩陣中如果矩陣的行i對應的單詞等於列j對應的單詞,那麼就在對應的m[i+1][j+1]位置等於m[i][j]+1
    再將m[i+1][j+1]與最大程度比較,從而找到最大值
    
    FirstString----字串1
    SecondString---字串2
    Longest--------最長公共子串的長度
    ComputeTimes--計算次數,用於對比計算量
    '''
    FirstStringLenght = len(FirstString)
    SecondStringLenght = len(SecondString)
    
    if (FirstStringLenght==0)|(SecondStringLenght)==0:
        return 
    
    Longest = 0
    ComputeTimes=0
    
    '''
    構建一個矩陣,行數為字串1的長度+1,列數為字串2的長度+1
    這裡+1,是為了計算方便,如果不+1,我們需要單獨對第一行,和第一列做一次迴圈,
    +1後,我們就可以捨去這段迴圈。
    主要是為了這個公式 m[i+1][j+1]=m[i][j]+1 服務
    '''
    m = np.zeros([FirstStringLenght+1,SecondStringLenght+1],dtype=np.int)
    
    for i in range(FirstStringLenght):
        for j in range(SecondStringLenght):
            if FirstString[i]==SecondString[j]:
                ComputeTimes+=1
                m[i+1][j+1]=m[i][j]+1
                if m[i + 1][j + 1] > Longest:
                    ComputeTimes+=1
                    Longest,SecondStringStartPoint = m[i + 1][j + 1],j+1
    return Longest,SecondString[SecondStringStartPoint-Longest:SecondStringStartPoint],ComputeTimes
  • 時間複雜度: O(n^2) ;
  • 空間複雜度:O(n^1)

測試階段繼續套用之前的str1和str2 依舊迴圈1000次,求平均。

In [19]:import time
    ...:start=time.clock()
    ...:for i in range(1000):
    ...:    LongestSubstring=LongestCommonSubstring(str1,str2)
    ...:print((time.clock()-start)/1000)
    ...:print(LongestSubstring)

Out[19]: 0.00026308407759279364
     ...: (9, '一家漏水,四家遭殃', 41) 

結果: 

  • 耗時: 0.00026308407759279364
  • 計算次數:41 

解法3-動態規劃求解法2(節約記憶體版):

import numpy as np
def LongestCommonSubstring(FirstString,SecondString):
    '''
    求最長子串解法2:
    建立一個以3乘字串2長度+1的矩陣
    矩陣中如果矩陣的行i對應的單詞等於列j對應的單詞,那麼就在對應的m[i+1][j+1]位置等於m[i][j]+1
    再將m[i+1][j+1]與最大程度比較,從而找到最大值
    
    FirstString----字串1
    SecondString---字串2
    Longest--------最長公共子串的長度
    ComputeTimes--計算次數,用於對比計算量
    '''
    FirstStringLenght = len(FirstString)
    SecondStringLenght = len(SecondString)
    
    if (FirstStringLenght==0)|(SecondStringLenght)==0:
        return 
    
    Longest = 0
    ComputeTimes=0
    
    '''
    構建一個矩陣,行數為3,列數為字串2的長度+1
    這裡+1,是為了計算方便,如果不+1,我們需要單獨對第一行,和第一列做一次迴圈,
    +1後,我們就可以捨去這段迴圈。
    主要是為了這個公式 m[cur+1][j+1]=m[pre+1][j]+1 服務
    '''
    '''
    實質上這個只是為了降低記憶體開銷,實質上和2是沒有差別的。
    因為我們只需要2列。通過比較m[cur+1][j+1]和Longest的大小
    就能確定最長子串的長度。從而記錄下這個長度的起點。
    '''
    
    m=np.zeros([3,SecondStringLenght],dtype=np.int)  # 只用兩行就可以計算最長子串長度
    for i in range(FirstStringLenght):
        # 通過且運算計算出當前行和先前行,實質是奇偶性對比
        cur,pre = int((i&1)==1),int((i&1)==0)
        for j in range(SecondStringLenght):
            if FirstString[i]==SecondString[j]:
                ComputeTimes+=1
                m[cur+1][j+1]=m[pre+1][j]+1
                if m[cur+1][j+1]> Longest:
                    ComputeTimes+=1
                    Longest,SecondStringStartPoint = m[cur+1][j+1],j+1
    return Longest,SecondString[SecondStringStartPoint-Longest:SecondStringStartPoint],ComputeTimes
  • 時間複雜度: O(n^2) ;
  • 空間複雜度:O(n^1)

測試階段繼續套用之前的str1和str2 依舊迴圈1000次,求平均。

In [19]:import time
    ...:start=time.clock()
    ...:for i in range(1000):
    ...:    LongestSubstring=LongestCommonSubstring(str1,str2)
    ...:print((time.clock()-start)/1000)
    ...:print(LongestSubstring)

Out[19]: 0.00027920729279321677
    ...: (9, '一家漏水,四家遭殃', 41)

 

結果: 

  • 耗時: 0.00027920729279321677
  • 計算次數:41 

 

參考資料:

從優化到再優化,最長公共子串