17.字符串匹配
字符串匹配基础(上):如何借助哈希算法实现高效字符串匹配?
BF算法
def index(s1,s2,start=0,end=None): if end == None: last = len(s1) else: last = end+1 assert start<=end i=start j=0 while i<last and j <len(s2): if s1[i]==s2[j]: i+=1 j+=1 else: i=i-j+1 j=0 if j>=len(s2): return i-len(s2) else: return -1 if __name__=="__main__": s1="1234567" s2="2" print(index(s1,s2,1,1))
""" 作为最简单、最暴力的字符串匹配算法,BF 算法的思想可以用一句话来概括, 那就是,我们在主串中,检查起始位置分别是 0、1、2…n-m 且长度为 m 的 n-m+1 个子串, 看有没有跟模式串匹配的。
BF 算法的时间复杂度很高,是 O(n*m),但在实际的开发中,它却是一个比较常用的字符串匹配算法。 原因有两点: 1.实际的软件开发中,模式串和主串的长度都不会太长。而且每次模式串与主串中的子串匹配的时候,当中途遇到不能匹配的字符的时候,就可以就停止了,不需要把 m 个字符都比对一下。所以,尽管理论上的最坏情况时间复杂度是 O(n*m),但是,统计意义上,大部分情况下,算法执行效率要比这个高很多。 2.朴素字符串匹配算法思想简单,代码实现也非常简单。简单意味着不容易出错,如果有 bug 也容易暴露和修复。在工程中,在满足性能要求的前提下,简单是首选。这也是我们常说的KISS(Keep it Simple and Stupid)设计原则。 所以,在实际的软件开发中,绝大部分情况下,朴素的字符串匹配算法就够用了. """
RK算法
""" RK 算法的思路是这样的: 我们通过哈希算法对主串中的 n-m+1 个子串分别求哈希值,然后逐个与模式串的哈希值比较大小。 如果某个子串的哈希值与模式串相等,那就说明对应的子串和模式串匹配了(这里先不考虑哈希冲突的问题,后面我们会讲到)。 因为哈希值是一个数字,数字之间比较是否相等是非常快速的,所以模式串和子串比较的效率就提高了。 通过哈希算法计算子串的哈希值的时候,我们需要遍历子串中的每个字符。 尽管模式串与子串比较的效率提高了,但是,算法整体的效率并没有提高。 解决方法: 假设要匹配的字符串的字符集中只包含 K 个字符,我们可以用一个 K 进制数来表示一个子串, 这个 K 进制数转化成十进制数,作为子串的哈希值。 特点: """ def index(s1,s2): "返回匹配值得第一个位置" dict_char = {"a":0,"b":1,"c":2,"d":3,"e":4,"f":5,"g":6,"h":7,"i":8,"j":9,"k":10,"l":11} radix_list=[] dict_substr={} len_s2=len(s2) len_dict=len(dict_char) for i in range(len_dict): radix_list.append(len_dict**i)#下标代表进位 h=0 for i in range(len_s2): h=h+dict_char[s1[i]]*radix_list[len_s2-i-1] dict_substr[s1[:len(s2)]] = [h,0]#求出第一个主串中子字符串的哈希值 for i in range(len(s1)-len(s2)): if s1[i+1:i + len(s2)+1] in dict_substr.keys(): continue#如果之前有存储直接跳过 h=(h-dict_char[s1[i]]*radix_list[len_s2-1])*len_dict + dict_char[s1[i+len_s2]] dict_substr[s1[i+1:i + len(s2)+1]] = [h,i+1] print(dict_substr) h1 = 0 for i in range(len_s2): h1 = h1 + dict_char[s2[i]] * radix_list[len_s2 - i - 1] for key,value in dict_substr.items(): if h1==value[0] and key==s2: #当一个子串的哈希值跟模式串的哈希值相等的时候,再比较一下串本身,防止哈希冲突。 return value[1] return False s1 = "deabeghijal" s2="d" print(index(s1,s2)) """ 设计的哈希算法是没有散列冲突的,也就是说,一个字符串与一个二十六进制数一一对应,不同的字符串的哈希值肯定不一样。 整个 RK 算法包含两部分,计算子串哈希值和模式串哈希值与子串哈希值之间的比较。第一部分,我们前面也分析了,可以通过设计特殊的哈希算法,只需要扫描一遍主串就能计算出所有子串的哈希值了,所以这部分的时间复杂度是 O(n)。 模式串哈希值与每个子串哈希值之间的比较的时间复杂度是 O(1),总共需要比较 n-m+1 个子串的哈希值,所以,这部分的时间复杂度也是 O(n)。所以,RK 算法整体的时间复杂度就是 O(n)。 有一个问题就是,模式串很长,相应的主串中的子串也会很长,通过上面的哈希算法计算得到的哈希值就可能很大,如果超过了计算机中整型数据可以表示的范围,那该如何解决呢? 假设字符串中只包含 a~z 这 26 个英文字母,那我们每个字母对应一个数字,比如 a 对应 1,b 对应 2,以此类推,z 对应 26。我们可以把字符串中每个字母对应的数字相加,最后得到的和作为哈希值。这种哈希算法产生的哈希值的数据范围就相对要小很多了。 不过,你也应该发现,这种哈希算法的哈希冲突概率也是挺高的。当然,我只是举了一个最简单的设计方法,还有很多更加优化的方法,比如将每一个字母从小到大对应一个素数,而不是 1,2,3……这样的自然数,这样冲突的概率就会降低一些。 哈希算法的冲突概率要相对控制得低一些,如果存在大量冲突,就会导致 RK 算法的时间复杂度退化,效率下降。极端情况下,如果存在大量的冲突,每次都要再对比子串和模式串本身,那时间复杂度就会退化成 O(n*m)。但也不要太悲观,一般情况下,冲突不会很多,RK 算法的效率还是比 BF 算法高的。 """
""" BF 算法是最简单、粗暴的字符串匹配算法,它的实现思路是,拿模式串与主串中是所有子串匹配,看是否有能匹配的子串。所以,时间复杂度也比较高,是 O(n*m),n、m 表示主串和模式串的长度。不过,在实际的软件开发中,因为这种算法实现简单,对于处理小规模的字符串匹配很好用。 RK 算法是借助哈希算法对 BF 算法进行改造,即对每个子串分别求哈希值,然后拿子串的哈希值与模式串的哈希值比较,减少了比较的时间。所以,理想情况下,RK 算法的时间复杂度是 O(n),跟 BF 算法相比,效率提高了很多。不过这样的效率取决于哈希算法的设计方法,如果存在冲突的情况下,时间复杂度可能会退化。极端情况下,哈希算法大量冲突,时间复杂度就退化为 O(n*m)。 """
字符串匹配基础(中):如何实现文本编辑器中的查找功能?
dict1={"w":1}
dict1.setdefault("w",2)
print(dict1.setdefault("z",3))#3
dict1.setdefault("w","weewew")
dict1["w"]=4
print(dict1)#{'w': 4, 'z': 3}
a="w"
print(a[1:])#""
#https://blog.csdn.net/chiang97912/article/details/83005577
#BM算法 def getBMBC(pattern,char=None):#没有亦可 # 预生成坏字符表 BMBC = dict() for i in range(len(pattern) - 1): char = pattern[i] # 记录坏字符最右位置(不包括模式串最右侧字符) BMBC[char] = len(pattern)-i-1 return BMBC def getBMGS(pattern): # 预生成好后缀表 BMGS = dict() # 无后缀仅根据坏字移位符规则 BMGS[''] = 0 #BMGS[''] = 1若单独用此方法必须=1,否则无限循环 for i in range(len(pattern)): # 好后缀 GS = pattern[len(pattern) - i - 1:] for j in range(len(pattern) - i - 1): # 匹配部分 NGS = pattern[j:j + i + 1] # 记录模式串中好后缀最靠右位置(除结尾处) if GS == NGS: BMGS[GS] = len(pattern) - j - i - 1 if not GS in BMGS.keys(): for k in range(1,i+1): if GS[k:] == pattern[:i-k+1]: BMGS[GS] = len(pattern)-i+k-1 break else: BMGS[GS] = len(pattern) return BMGS def BM(string, pattern): """ Boyer-Moore算法实现字符串查找 """ m = len(pattern) n = len(string) i = 0 j = m indies = [] BMBC = getBMBC(pattern=pattern) # 坏字符表 BMGS = getBMGS(pattern=pattern) # 好后缀表 while i < n: while (j > 0): if i + j -1 >= n: # 当无法继续向下搜索就返回值 return indies # 主串判断匹配部分 a = string[i + j - 1:i + m] # 模式串判断匹配部分 b = pattern[j - 1:] # 当前位匹配成功则继续匹配 if a == b: j = j - 1 # 当前位匹配失败根据规则移位 else: char = string[i + j - 1] for k in range(len(pattern[:j-1])): if pattern[k] == char: dist = j-k-1 else: dist = j #print(BMGS.get(b[1:])) # print(dist) #input() #i = i + BMGS.get(b[1:]) i = i + max(BMGS.get(b[1:]), dist) j = m # 匹配成功返回匹配位置 if j == 0: indies.append(i) i += 1 j = len(pattern) s1="ddddddd" s2="acddddddddddde" print(BM(s2,s1)) print(getBMGS(s1))
def Index_KMP(s1,s2,pos=0): next = get_next(s2) i = pos j = 0 while i<len(s1) and j<len(s2): if j==-1 or s1[i] == s2[j]: i+=1 j+=1 else: j = next[j] if j>=len(s2): return i-len(s2) else: return 0 def get_next(s2):#匹配到第i位不等,前i-1位的最长公共子序列 i=0 next=[-1]#第一位就不等时,输出为-1,不包含序列本身 j = -1 while (i<len(s2)-1):#最多比较到最后一位不等时,前i-1位的最长公共子序列(不包含序列本身),最后一位不需要比 if j==-1 or s2[i] == s2[j]:#j==-1,表示没有任何相同的子序列,此时i+1,j+1=0,即i+1位置和j=0位置开始匹配 #若s2[i] == s2[j],i-1和j-1位置肯定相等,不然不会到这一步 #i+1之前的相同个数即为j+1 i+=1 j+=1 next.append(j)#i+=1和next.append(j)是同步的 else: j = next[j]#j之前匹配的次一级最长公共子序列 return next if __name__ == "__main__": s1 = "acabaabaabcacaabc" s2 = "abaabcac" print(Index_KMP(s1,s2))
KMP链接1:http://www.cnblogs.com/SYCstudio/p/7194315.html
KMP链接2:https://www.cnblogs.com/zrdm/p/8590670.html
浙公网安备 33010602011771号