KMP 示例
2020-05-06 本文已影响0人
过桥
1、问题
检查字符串中是否包含子字符串
main_string = 'abcxabcdabcdabcy'
sub_string = 'abcdabcy'
2、关键字实现
方法一、find
# 关键字 find ,找到返回索引,没找到返回 -1
print("内置方法,find,索引为:",main_string.find(sub_string))
方法二、index
# 关键字 index ,找到返回索引,没找到返回 Error,ValueError: substring not found
try:
print("内置方法,index,索引为:",main_string.index(sub_string))
except Exception as e:
print("内置方法,index未找到,返回异常:",str(e))
3、简单实现方法
特点,简单,低效
核心,按主串字符索引,逐个完整匹配
方法一、暴力递归
def violent_search(main_string,sub_string,index=0):
'''
暴力查找,递归
主字符串、子字符串、当前检索索引,默认为0
'''
main_l = len(main_string)
sub_l = len(sub_string)
if main_l < sub_l + index:
return -1
elif main_string[index:sub_l+index] == sub_string:
return index
else:
index += 1
return violent_search(main_string,sub_string,index)
print("暴力查找,递归,索引为:",violent_search(main_string,sub_string))
方法二、循环迭代
def violent_search_next(main_string,sub_string):
'''
暴力查找,for循环
主字符串、子字符串
'''
main_l = len(main_string)
sub_l = len(sub_string)
for i in range(main_l-sub_l+1):
if main_string[i:sub_l+i] == sub_string:
return i
return -1
print("暴力查找,循环,索引为:",violent_search_next(main_string,sub_string))
4、KMP
实现
发现者,D.E.Knuth,J.H.Morris,V.R.Pratt
时间复杂度,O(m+n)
核心,利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的
步骤一、构建部分匹配表
def kpm_gen_while(substring):
"""
构造临时数组pnext,用于计算 “部分匹配表”
"""
index = 0
m = len(substring)
pnext = [0]*m
i = 1
while i < m:
if (substring[i] == substring[index]):
pnext[i] = index + 1
index += 1
i += 1
elif (index!=0):
index = pnext[index-1]
else:
pnext[i] = 0
i += 1
return pnext
def kpm_gen_for(substring):
m = len(substring)
pnext = [0]*m
# 首字符前缀和后缀都为空集,共有元素的长度为0,循环从 1 开始
for i in range(1,m):
temp = substring[0:i+1]
# print("判断字符串,",temp)
for j in range(1,len(temp)):
# print("前缀,",temp[0:j],"后缀,",temp[-j:])
if temp[0:j] == temp[-j:]:
pnext[i] = j
return pnext
print(kpm_gen_while(sub_string))
print(kpm_gen_for(sub_string))
两种构建方式,区别不大,都是根据子串,计算前缀、后缀,记录基于前缀,后缀中重复最长的字段长度。
例如 "ABCDABD"
"A"的前缀和后缀都为空集,共有元素的长度为0;
"AB"的前缀为[A],后缀为[B],共有元素的长度为0;
"ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;
"ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;
"ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;
"ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;
"ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。
部分匹配表 即为 0 0 0 0 1 2 0
步骤二、实现比对方法
def kmp_match_for_else(main_string, sub_string):
m = len(main_string)
s = len(sub_string)
cur = 0 #起始指针cur
table = kpm_gen_for(sub_string)
while cur <= m - s:
for i in range(s):
print("当前指针 ",cur,"子字符串中索引 ",i,"主值 ",main_string[i+cur],"辅值 ",sub_string[i])
if main_string[i+cur] != sub_string[i]:
cur += max(i - table[i-1], 1)
break
else:
return cur
return -1
def kmp_match(main_string, sub_string):
m = len(main_string)
s = len(sub_string)
cur = 0 #起始指针cur
table = kpm_gen_for(sub_string)
while cur <= m - s:
for i in range(s):
# print("当前指针 ",cur,"子字符串中索引 ",i,"主值 ",main_string[i+cur],"辅值 ",sub_string[i])
if main_string[i+cur] != sub_string[i]:
#根据匹配表,移动多位 ,移动位数 = 已匹配的字符数 - 对应的部分匹配值(最后一次相等的值即,i - 1)
cur += max(i - table[i-1], 1)
break
elif i == s-1:
# 如果子字符串最后一位也匹配上,返回当前索引
return cur
return -1
print("KMP查找,索引为:",kmp_match(main_string,sub_string))
两种写法区别在于第一种使用了 Python
中 for ... else
语法,考虑其他语言可能不支持,第二种使用了常规elif