题目描述:
KMP 算法是在 s中查找子串 p,如果存在,返回这个子串的起始索引,否则返回空列表
示例:
输入:文本串s='BBC ABCDAB ABCDABCDABDE',模式串p='ABCDABD'
输出:[15]
解释:起始索引等于 15 长度为 7 的子串是 "ABCDABDE"
思路:
s='ABCDABCDABD'
p='ABCDABD'
当确定s的第7个元素和p的第7个元素不同时,下一次比较希望能如下
ABCDABCDABD
ABCDABD
这样我们就需要求得模式串p前缀后缀的公共元素的最大长度表
代码:
# KMP 算法是在 s中查找子串 p,如果存在,返回这个子串的起始索引,否则返回空列表
# 输入:文本串s='BBC ABCDAB ABCDABCDABDE',模式串p='ABCDABD'
# 输出:[15]
# 解释:
# 起始索引等于 15 长度为 7 的子串是 "ABCDABDE"
# 思路:
# s='ABCDABCDABD'
# p='ABCDABD'
# 当确定s的第7个元素和p的第7个元素不同时,下一次比较希望能如下
# 'ABCDABCDABD'
# 'ABCDABD'
# 这样我们就需要求得模式串p前缀后缀的公共元素的最大长度表
def get_next(p,p_len):
# 得到模式串p前缀后缀的公共元素的最大长度表p_next
# 例如:
# p='ABCDABD'
# p_next=[0,0,0,0,1,2,0]
# 解释:
# 前缀后缀解释: 对于'ABC'-->前缀:'A','AB';后缀:'C','BC'
# p_next[i] --> i==0时,'A'前缀后缀的公共元素的最大长度为0
# i==1时,'AB'前缀后缀的公共元素的最大长度为0
# i==2时,'ABC'前缀后缀的公共元素的最大长度为0
# i==3时,'ABCD'前缀后缀的公共元素的最大长度为0
# i==4时,'ABCDA'前缀后缀的公共元素'A'长度为1
# i==5时,'ABCDAB'前缀后缀的公共元素'AB'长度为2
# i==6时,'ABCDABD'前缀后缀的公共元素的最大长度为0
#p_next的第一个元素必定为0
p_next=[0]*p_len
for i in range(1,p_len):
#确定next[i]值时,我们只需比较前一状态前缀后缀的公共元素的下一位元素和当前位元素是否相同
#例如i==5时,此时我们关注的字串位'ABCDAB',next=[0, 0, 0, 0, 1, 0, 0],
# 我们已求得i=4时前缀后缀的公共元素'A'长度为1,我们这里比较p的第2个元素'B'是否等于p的第6个元素'B'
# 因为我们已经在i=4时已经确定了p[:1]==p[5-1:5]
if p[i]==p[p_next[i-1]]:
#若相同则是前一状态的长度加一
p_next[i]=p_next[i-1]+1
#不相等时判断是否和首位元素是否相同
elif p[i]==p[0]:
p_next[i]=1
#不然就是无公共元素
else:
p_next[i]=0
return p_next
def KMP(s,p):
# 文本串s的长度
s_len=len(s)
# 模式串p的长度
p_len=len(p)
# s,p搜索时的下标
s_index,p_index=0,0
# 得到模式串p前缀后缀的公共元素的最大长度表p_next
p_next=get_next(p,p_len)
# 存储结果列表
res=[]
# 用s_index来控制循环结束
while s_index<s_len:
# 用p_index来控制s_index的变化以及判断是否匹配成功
if p_index<p_len:
# 若s的当前字符和p的当前字符相同,则比较s的下一字符和p的下一字符
if s[s_index]==p[p_index]:
s_index+=1
p_index+=1
# 若s当前字符是和p的首字符比较,且不相同,则比较s的下一字符和p的首字符
elif p_index==0:
s_index+=1
# 若s和p已比较了一些字符相同,在当前字符不相同时,则根据p_next确定p_index
else:
# 例如:
# s = 'ABCDABC'
# p = 'ABCDABD'
# s_index=6
# p_index=6
# p_next=[0,0,0,0,1,2,0]
# 这里我们要的结果就是s_index=6,p_index=2
# 即我们希望下一次这样比较(比较的是对齐的两个C)
# ABCDABC
# ABCDABD
p_index=p_next[p_index-1]
# p_index==p_len时说明字符完全匹配成功,存储初始下标并继续比较
if p_index==p_len:
# 当前s的下标减去模式串s的长度就是匹配成功初始的下标
res.append(s_index-p_len)
# 跟上面得到p_index一样的方式继续匹配,因为这里已经匹配成功了,所以直接是p_next的最后一个元素
p_index=p_next[-1]
return res
s='BBC ABCDAB ABCDABCDABCD'
p='ABCDABCD'
res=KMP(s,p)
print(res)
for begin in res:
print(s[begin:begin+len(p)])