【KMP算法(烤馍片,真香)】 KMP 暴力算法 KMP算法

KMP算法,又称烤馍片算法,是字符串匹配的改良算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。

我们就在一步一步的实践探索中来理解这个神奇的算法吧

说到字符串匹配,就是给你两个串,看一个文本串里是否含有一个模式串(文本串为root,模式串为s)

暴力算法

暴力算法我们都会,一位一位的比较嘛,不一样向后移动就行了

下面是一个例子

【KMP算法(烤馍片,真香)】
KMP
暴力算法
KMP算法

我们拿到这个串的时候,就先让s[1]和root[1]对齐(下标从1开始)

当前四位都匹配成功的时候(abca)我们匹配第五位,第五位不匹配,按照暴力的算法我们就会让s[1]和root[2]对齐

【KMP算法(烤馍片,真香)】
KMP
暴力算法
KMP算法

这样的话roo里面的指针就会回退,这是对已知信息的极大的浪费,

我们有没有一种方法能利用已匹配的信息让root的指针不回退,从而让s(模式串)直接移动到我们想要的位置呢?

答案是有的,那就是烤馍片算法

KMP算法

如何操作

我们再拿样例来分析一下

【KMP算法(烤馍片,真香)】
KMP
暴力算法
KMP算法

 我们匹配好前面四位的时候,发现第五位不对,我们就移动模式串

根据前面已知的,我们就可以像下面这样

【KMP算法(烤馍片,真香)】
KMP
暴力算法
KMP算法

 直接向后移动两位,然后我们的roo指针继续从第五位开始,直到匹配成功

这里有一个前缀串和后缀串的知识

如abcde

前缀串:a,ab,abc,abcd,abcde

后缀串:e,de,cde,bcde,abcde

我们要求它们除了自己本身的最长公共前后缀

例如这里,除了本身就没有了

现在不懂没关系,接着往下

我们发现,当匹配不成功的时候,我们root指针所指的位置前面都是匹配的

如上面的abab

这里的最长公共前后缀(除了自己)就是ab

所以我们就可以一次性移动两位

但是这样会不会有中间漏掉的情况呢?(下面的......是省略其中的字符)

【KMP算法(烤馍片,真香)】
KMP
暴力算法
KMP算法

例如这个,我们现在已知匹配了的它除了自己的最长公共前后缀是***

当我们前面的都匹配完了:

【KMP算法(烤馍片,真香)】
KMP
暴力算法
KMP算法

匹配下一位的时候,发现不对,我们就可以直接把模式串开头的下标向后移动到

【KMP算法(烤馍片,真香)】
KMP
暴力算法
KMP算法

 我们假设其中有一种情况

【KMP算法(烤馍片,真香)】
KMP
暴力算法
KMP算法

 使得这样可以匹配成功(root指针还是在已匹配的那里)

【KMP算法(烤馍片,真香)】
KMP
暴力算法
KMP算法

这里就相等

由之前可知

【KMP算法(烤馍片,真香)】
KMP
暴力算法
KMP算法

 这两个相等

所以最长公共前后缀(除去他自己)就是框起来的玩意儿了,但是我们已知的并不是那个,所以这是矛盾的,所以也就不需要担心上述情况的发生了

通过这里,我们就可以解释一下为什么要除去他自己了,因为我们移动是从最长公共前后缀的前缀移动到后缀,但是如果算上自己的话,相当于没有移动,这就是没有意义的。懂?

我们就可以用一个next数组来存储每一个区间的最长公共前后缀的长度了

再往回看,发现没有,匹配成功的都是模式串的前缀串,所以我们的next数组只需要记录模式串前缀串的最长公共前后缀了

既然理解了KMP的操作方式,我们来看看怎么实现

如何实现

我们next[i]表示模式串1到i的最长公共前后缀的长度

【KMP算法(烤馍片,真香)】
KMP
暴力算法
KMP算法

 首先next[1]为0(这个很好理解吧,就一个字符,除去自己就没了)

我们再看第二位,我们现在有两个指针,一个i指当前处理的位置,然后一个j表示1---i-1的最长公共前后缀的长度。为什么要j呢?

你想想,我们前面已经匹配好了,如果新加入进来的,也就是i指针所指的,和已知最长的下一位相等,我们不就可以避免重头计算而是很好的利用了

【KMP算法(烤馍片,真香)】
KMP
暴力算法
KMP算法

 这里i和j的下一位不相等,所以就是

next[2]=j

【KMP算法(烤馍片,真香)】
KMP
暴力算法
KMP算法

相等

j++

next[i]=j

【KMP算法(烤馍片,真香)】
KMP
暴力算法
KMP算法

相等

j++

next[i]=j=2

【KMP算法(烤馍片,真香)】
KMP
暴力算法
KMP算法

j++

next[i]=j=3

 【KMP算法(烤馍片,真香)】
KMP
暴力算法
KMP算法

 到这里不相等了,意思是我们只能继续寻找比现在长度小的公共前后缀了,但是怎么找呢?

这个例子不明显,我们换一个

【KMP算法(烤馍片,真香)】
KMP
暴力算法
KMP算法

 这是已经处理的,现在j等于3,表示当前除了本身的最长公共前后缀长度为3

【KMP算法(烤馍片,真香)】
KMP
暴力算法
KMP算法

 但是这样的话,就不能继续了,我们就要找第二长的公共前后缀了,但是怎么找?

我们来假设一下

【KMP算法(烤馍片,真香)】
KMP
暴力算法
KMP算法

 【KMP算法(烤馍片,真香)】
KMP
暴力算法
KMP算法

 现在这是已知的长度为x

当我们想找到一个长度小于x的时(黄色部分)由于这个的长度小于x并且两个黑框框相等,所以我们就可以合并

【KMP算法(烤馍片,真香)】
KMP
暴力算法
KMP算法

【KMP算法(烤馍片,真香)】
KMP
暴力算法
KMP算法

 代换过来就是这样,现在就在同一个框框里了,所以我们只需要求next[x]的最长的公共前后缀就行了,懂?不懂得可以私信的,这里是最难的地方

下面就是代码

 1 void getnext()
 2 {
 3     int j=0;//初始为0 
 4     for(int i=2;i<=n;i++)
 5     {                        //这里就是查找第二长的 回退操作 
 6         while(j!=0&&s[i]!=s[j+1])j=next[j];//如果j等于0的话,我们直接比较就行了,因为不存在第二长的 
 7         next[i]=(s[j+1]==s[i]?++j:j);//判断是否相等 
 8     }
 9     return ;
10 }

我们康康全局吧

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=1000010;
 4 int n;
 5 int next[N];
 6 char root[N],s[N];
 7 vector<int> ans; 
 8 void getnext()
 9 {
10     int j=0;//初始为0 
11     for(int i=2;i<=n;i++)
12     {                        //这里就是查找第二长的 回退操作 
13         while(j!=0&&s[i]!=s[j+1])j=next[j];//如果j等于0的话,我们直接比较就行了,因为不存在第二长的 
14         next[i]=(s[j+1]==s[i]?++j:j);//判断是否相等 
15     }
16     return ;
17 }
18 void kmp()
19 {
20     int j=0;
21     int len1=strlen(root+1);
22     int len2=strlen(s+1);
23     for(int i=1;i<=len1;i++)
24     {
25         while(j!=0&&root[i]!=s[j+1]){j=next[j];} 
26         if(root[i]==s[j+1])j++;//j是已匹配的长度 
27         if(j==len2)ans.push_back(i-len2+1),j=next[j];//长度达到了就记录,但是我们还要继续找, 所以回退 
28     }
29 }
30 int main()
31 {
32     scanf("%s%s",root+1,s+1);//输入 
33     n=strlen(s+1);//记录长度 
34     getnext();//整理next数组 
35     kmp();//比较 
36     for(int i=0;i<ans.size();i++)
37         printf("%d
",ans[i]);
38     cout<<next[1];
39     for(int i=2;i<=n;i++)
40         printf(" %d",next[i]);
41     return 0;
42 }

好的,一份精美的烤馍片就做好了!!!