KMP算法是做什么的
字符串匹配,查询文本串中是否包含模式串。
例子
文本串:aabaabaaf
模式串:aabaaf
一开始肯定是文本串和模式串的头部先对齐,然后逐个元素比较。比较相等的情况就继续比较下一个元素,关键在不相等的时候。不相等的话就要将已经匹配的进度进行回退。关键是回退到哪里? 暴力匹配就是回退到最开始。能否根据一些信息少回退一部分提高效率呢?
最长相等前后缀
要回答上面的问题,要借助一个新的概念:最长相等前后缀。假设模式串为s,长度为n,那么要分别计算s[:1],s[:2],s[:3]…s[:n]这n个逐渐变长的子串的最长相等前后缀。
前缀指从字符第一位开始,不以字符最后一位结束的所有子串。例如aabaa的前缀包括a,aa,aab,aaba。
后缀指以字符最后一位结束,不以字符第一位开始的所有子串。例如aabaa的后缀包括a,aa,baa,abaa。
例如上面的模式串s=‘aabaaf’
子串下标 | 子串 | 最长相等前后缀 | 长度 |
---|---|---|---|
s[0:1] | a | - | 0 |
s[0:2] | aa | a | 1 |
s[0:3] | aab | - | 0 |
s[0:4] | aaba | a | 1 |
s[0:5] | aabaa | aa | 2 |
s[0:6] | aabaaf | - | 0 |
当遇到不匹配的时候,直接回退到前面已经匹配成功的子串的最长相等前后缀的后面继续匹配。例如模式串aabaaf匹配文本串aabaabaaf,第一次不匹配出现在b和f,前面的aabaa已经匹配,根据表格,下一次就从最长相等前后缀aa的后面开始匹配(aa是文本串中的后缀,模式串的前缀,分别从他们两个后面开始)。
代码实现
next数组
next数组保存的就是最长相等前后缀的长度
模式串aabaaf的原始next数组应为
[0, 1, 0, 1, 2, 0]
具体代码实现如下
j代表前缀末尾下标,i代表后缀末尾下标,都是正常理解的从前往后看的末尾。理解后缀末尾的时候要注意,子串的末尾位置就是i,i是一直向后遍历的,而j是根据情况,继续向后匹配或者回退。
s = 'aabaaf'
def getNext(s):
n = len(s)
next = [0] * n
j = 0 #前缀末尾下标为0。!!!同时j + 1是最长相等前后缀的长度
#长度为1的子串不考虑,从长度为2的子串开始。根据后缀末尾i遍历,初始后缀末尾下标为1。
for i in range(1, n):
#前后缀不相等的情况,要连续回退j,直到满足条件或不能回退
while s[i] != s[j] and j > 0:
j = next[j - 1]
#先回退,再检查能否再匹配
if s[i] == s[j]:
next[i] = j + 1
j += 1 #其实i和j都加一了,i是在for循环那加一
return next
print(getNext(s))
利用得到的next数组进行匹配
text = 'aabaabaaf'
s = 'aabaaf'
def matching(text, s):
'''
参数:text代表文本串,s代表模式串,如果存在,返回匹配处开始的索引
'''
n = len(text)
s_next = getNext(s) #getNext就是上面的
j = 0
'''
j是已匹配的长度,j - 1是已匹配的最后一位索引
下面每次for循环都是比较text[i]和s[j]
'''
for i in range(n):
while text[i] != s[j] and j > 0: #不匹配时,注意也是连续回退
j = s_next[j - 1] #j不匹配,j - 1及之前都匹配了,找前面的已经匹配的最长相等前后缀长度
if text[i] == s[j]:
j += 1 #模式串中的索引后移,文本串中的索引后移通过for循环实现的
if j == len(s): #模式串中的索引超出了模式串长度(模式串下标为len(s) - 1),说明模式串全部匹配。此时i的位置是模式串的最后一个!!!j加一了,i还没呢
return i - len(s) + 1
return -1 #没找到,返回-1
本文是跟随代码随想录公众号的学习心得,真的是不错的公众号,carl哥牛的。