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 

 

参考资料:

从优化到再优化,最长公共子串

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值