KMP算法???
kmp算法最简单的就是用来匹配子串,也就是从字符串s1中找到s2出现的次数与位置。同时,kmp的nxt数组还有很多高能的用处。
NXT数组:
nxt数组是kmp算法中极其重要的部分,nxt[i]表示子串s中,上一次s[i]为后缀的位置。(看了后面的原理就很明白了)
大致原理:
举个例子:假如母串s1为abcabcaba 子串s2为abcaba
如果进行暴力匹配的话,就是先从母串第一个字母开始匹配,s1[1]=s2[1],继续找s1[2]=s2[2],继续,s1[3]=s2[3]......s1[5]=s2[5],然后s1[6]!=s2[6]!!!!!重新开始,s1[2]!=s2[1].........这就使复杂度瞬间增高了,很容易被卡成O(len(s1)*len(s2))
然后来看kmp如何处理。先找出s2的nxt数组,上面这个样例中nxt[1]=0,nxt[2]=0,nxt[3]=0,nxt[4]=1,nxt[5]=2,nxt[6]=4。
然后按照与暴力相同的方法往后匹配。当匹配到s1[5]时,发现下一个就不匹配了,先别着急从新开始,这是就用到了nxt数组,虽然下一个不匹配的,但是不代表前面的努力都白费了。至少说明s1的当前位置与s2的当前位置是匹配的。然后我们可以从s2[2]开始继续往下与s1的s1[5]匹配,观察下一个元素是否匹配,这时发现s1[6]与s2[3]是匹配的,继续往下匹配,最后发现完全匹配。任务完成。
两者比较而言,kmp减少了每次匹配不成功时重新匹配的麻烦,而是借助于先前的努力继续匹配,这样就大大节省了时间。这一优点在s2的元素变得更多时,体现的更明显。
大致过程:
kmp算法的大致过程,分为两步。
第一步:
子串的自身匹配。也就是找nxt数组,找法与kmp的匹配原理类似。
还是上面那个例子:abcaba
假如说当前处理到了nxt[3](也就是从1到2的nxt都处理好了),因为在s[3]之前没有'c',所以nxt[3]=0,然后去处理s[3]的下一位s[4],s[4]与nxt[3]的下一位正好匹配,所以nxt[4]=nxt[3]+1=1,然后匹配s[5],发现s[5]与nxt[4]的下一位正好匹配,所以nxt[5]=nxt[4]+1=2然后是s[6],发现nxt[5]的下一位与s[6]不同。所以去找nxt[5]的nxt,也就是0,然后发现0的下一位与s[6]匹配,所以nxt[6]=0+1=1。这里为什么nxt[6]不是4??因为不能保证从s[1]到s[4]与从s[3]到s[6]相同,如果这里写成4的话,按照上面的原理进行匹配,很明显会匹配出错误来,模拟一下就很明白了。
第二步:
母串与子串的匹配。按照上面的原理进行操作即可,具体就是用一个模拟指针p来指向当前匹配到的子串中的哪一位置,当p指向了子串的末端,那么说明完成了一个匹配。这是要将指针指向当前指针的nxt。
一道板子题:luogu3375
代码:
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 using namespace std; 5 const int N=1000010; 6 int nxt[N],f[N],p; 7 int l1,l2; 8 char s1[N],s2[N]; 9 void get_nxt() 10 { 11 p=0;nxt[1]=0; 12 for(int i=2;i<=l2;++i) 13 { 14 while(p>0&&s2[i]!=s2[p+1]) p=nxt[p]; 15 if(s2[i]==s2[p+1]) p++; 16 nxt[i]=p; 17 } 18 } 19 void kmp() 20 { 21 p=0;int ans=0; 22 for(int i=1;i<=l1;++i) 23 { 24 while(p>0&&s1[i]!=s2[p+1]) p=nxt[p]; 25 if(s1[i]==s2[p+1]) p++; 26 if(p==l2) 27 { 28 ans++; 29 p=nxt[p]; 30 printf("%d\n",i-l2+1); 31 } 32 } 33 return ; 34 } 35 int main() 36 { 37 scanf("%s%s",s1+1,s2+1); 38 l1=strlen(s1+1); 39 l2=strlen(s2+1); 40 get_nxt(); 41 kmp(); 42 for(int i=1;i<=l2;++i) 43 printf("%d ",nxt[i]); 44 return 0; 45 }