kmp-学习理解记录

Galina ·
更新时间:2024-09-21
· 998 次阅读

本片是在回忆kmp的时候写下的,最近老师再讲这个,又发现之前写的都忘的差不多了在这里从新写一下笔记,纯小白文字:

基础暴力:

再看kmp的时候我们先来讲讲常规的模式串的暴力匹配,对于常规的一个文本串S以及一个匹配的字串P,最简单的想法就是两个指针,从S和P的头开始匹配,两个指针i,j同时开始出发,然后当i和P的第一个字符匹配的时候然后i,j同时递增,当j走到P的最后,字符串的匹配就完成了,但是如果中间某一段不匹配了就需要回到第一个匹配到P的首字母的i的位置,光打字可能有点难以理解,所以还是拿图说话,这里假设S的字符串为easdkjfassdfwe,然后需要匹配的模式串P为ass。

ok,第一步,i和j默认都是从队列首开始

第二步,明显e和a不匹配,所以i要加1

第三步,a和a匹配上了,所以这个时候,i和j同时向后走

第四步,i位和j位的s又匹配上了,所以i和j继续往后移一位

第五步,i位置和j位,d和s不匹配,所以i需要往后退j-1个位置,然后j重置为最前面的位置

第六步,然后重复上面的过程,直到匹配到了下一个和j位的a的位置,如下

第七步,还是当匹配到了头之后,i和j一起往后走

知道最后,j=模式串P的长度-1的时候,说明匹配完成了,就能得出结论,匹配成功了,但是其实算法还是可以有优化的,当前这个模式串可能看不出来,但是如果把文本串换成abcabcabe,模式串换成abe,那每次回到头的操作其实就是损失了大量的效率,在文本串模式串都比较短的情况下,优化的效果可能没有那么明显,但是一旦数量变大了之后,时间优化是很明显的,所以这就是下面要回忆的kmp算法

kmp算法

这个算法里最重要是一部步骤就是,你要怎么确定,在回溯的时候,往回跳几个位置,而不是每次都是回退j-1的位置,这里就涉及到一个部分匹配表(Partial Match Table),他会在你每次匹配部分的时候,根据已知的信息来告诉你i应该往后退几位是最快的,涉及到部分匹配的时候,这里又要提到一个新的概念,前缀和后缀,"前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。这么说可能有点干吧,还是上图,我们假设需要匹配的模式串为qwerqw,然后我们需要求出他的部分匹配表

我们可以看到q的前缀和后缀都是空的所以q的值便是0,然后就qw,由于qw前缀只有一个w后缀只有一个q所以w下面的值依旧是0

接下来是qwe,也可以看到完全没有相同的前缀和后缀所以依旧是0

接下来是qwer

然后是qwerq,我们也可以看到有两个q相同了,所以q的位置为1

最后是qwerqw,

所以整个部分匹配表就得到了,下面是python的写法

把他打包成一个函数了,接受的参数是一个字符串返回是一个数组

def back_next(string): back_next = [0 for i in range(len(s))]#创建一个结果数组 for i in range(len(s)): #循环字符串 tep = 0#把将要填入结果的匹配字符串长度初始化 if i==0:#如果是第一个直接返回 continue for j in range(i):#切割字符串,每次拿前面j个和后面j个比较 t = s[:i+1]#创建一个临时数组来存放每次需要判断的字符串 if t[:j+1]==t[len(t)-j-1:]:#如果前i+1个和后i+1个相同,则把长度填入结果中 tep = j+1 back_next[i]=tep return back_next

核心讲完了,然后就是怎么使用的方法了匹配的结果无非就是三种情况,第一种i位置和j位置的一直不相等,那这没啥好说了就是i递增就完事,第二种:i位置和j位置相等,那就是i和j同时递增这也简单,第三个就是,i位置和j位置前半部分相等,后半部分不相等,这个时候j需要退到不匹配的前一个位置也就是j = pnext[j-1]至于为啥要退到这,简单来说就是pnext[j-1]是重复的个数,下面上代码

def kmp(string,substring): pnext = back_next(substring) print(pnext) m,n,i,j = len(string),len(substring),0,0 while i<m and j<n: if string[i] == substring[j]: i+=1 j+=1 elif j!=0: j = pnext[j-1] else: i+=1 return j==n def back_next(string): back_next = [0 for i in range(len(string))] for i in range(len(string)): tep = 0 if i==0: continue for j in range(i): t = string[:i+1] if t[:j+1]==t[len(t)-j-1:]: tep = j+1 back_next[i]=tep return back_next
作者:王与弓长



学习 kmp

需要 登录 后方可回复, 如果你还没有账号请 注册新账号