KMP的套路
前言
KMP 算法是一个非常著名且高效的字符串匹配算法,
例如典型的使用场景:已知被匹配串“abaabaabca",匹配串为“abaabca”.然后判断被匹配串中是否存在匹配串,如果有返回被匹配串中匹配串的起始坐标,否则返回-1
`
一、暴力解法
常规BF思想就是套两层循环以此检索。每次匹配不成功匹配串都会返回起始点,但是若使用了kmp则不一样。
二、KMP思想(模板)
1、了解最长公共前后缀:
就例如这段字符串abaabca
||前缀 |后缀|最长公共前后缀|
|--|--|--|--
| a|无 |无|无|
|ab|a|b|0||
|aba|a、ab|a、ba|a|
|abaa|a、ab、aba|a、aa、baa|a
|abaab|a、ab、aba、abaa|b,ab,aab,baab|ab
|......|...|...|...
而最长公共前后缀有什么用呢?比如刚刚的例子如果纯bf那么当不匹配时
匹配串只能回到起始点继续匹配,但是用这个思想
abaabaabca
abaabca
在匹配到第五个字符时匹配失败,按常理就只能返回起始点了对吧,但是
abaab的最长公共前后缀是2,所以当匹配失败时可以利用这个让匹配串的下标只需返回到2的位置也就是a
那么为什么最长公共前后缀就能计算出,转移目标呢?
假设某个字符串 s 的最长共前后缀为 X="abcd...",
那么这个字符串一定是一下结构,开头是 X 结尾是 X 中间可能会有重叠,匹配到 s 的最后一个字符失败后,那我们知道 X 肯定是匹配成功了,因为 X 不包含最后一个字符。既然我们知道 X 匹配成功,那么我们一定知道,在主串中一定是从 i 位置开始且一定有一个 X 与模式串中的 X 匹配成功。
2、求得Next数组的模板(每个下标以前字符串的最长公共前缀)
def getNext(s):
next = [0] * len(s)
next[0] = -1
l = len(s)
j = 0 # 后缀位置
k = -1 # 前缀位置
while j < l - 1:
if k == -1 or s[j] == s[k]: # 若前后缀相同或者前缀位置返回最开始
k += 1
j += 1
next[j] = k # 记录
else:
k = next[k] # 动规思想,返回上一个k位置的状态
return next
其中最难理解的就是k=next[k]
,这行代码实质上是动态规划的一个思想
KMP整体模板
s = input()
p = input()
def getNext(p):
next=[0]*len(p)
pLen = len(p)
next[0] = -1
k = -1
j = 0
while j < pLen - 1:
# p[k]表示前缀,p[j]表示后缀
if k == -1 or p[j] == p[k]:
j += 1
k += 1
next[j] = k
else:
k = next[k]
return next
next = getNext(p)
print(next)
ss = 0 # 被匹配串位置
ps = 0 #匹配串位置
sl = len(s)
pl = len(p)
while ss < sl and ps < pl:
if ps == -1 or s[ss] == p[ps]: 若能匹配或者ps从头开始匹配
ps += 1
ss += 1
else:
ps = next[ps] #无法匹配则让ps返回他当前公共前后缀长度的位置
if ps == pl: #说明匹配成功
print(ss-ps)
else:
print(-1)