KMP——杨子曰算法
半年过后我又回来了
先给一道题:
给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置。
(字符串长度<=1000000)
大佬说:“哟,这道题的暴力好优啊!!”
就像下面这样指针i,j从头开始
哦,一样,i++,j++
嗯,又一样,i++,j++
继续
什么,不一样了,没事j从头来,i从第二个出发
一开始就不一样了,那再向前一格
……
于是乎后面的开头比一个就out了,如此之快(妙啊,妙啊!)
这时从天而降一组数据叫做:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaa
凉凉~
KMP走起:
杨子曰:失败是成功之母。我们可以想想看之前的匹配能不能为之后的操作提供一些有用的信息:
比方说这样的两个字符串:
比到黄色格子发现不一样了,我们完全没有必要把j调到最开头,我们可以这样继续去比:
因为ab本身就在之前的匹配中匹配上了,也就是说对于字串中的每一个位置,我们可以找到这个位置前的部分中最长的前缀和后缀相同的长度,这样一来当当前的匹配无法进行时可以将它的最长前缀和后缀对齐,我们不妨把这个匹配不上,就需要移动的下一个位置开一个数组,叫next
具体实现可以再欣赏一下
如图:
已经匹配成这样,发现红色位置的字符匹配不上:
然而黄色部分都是相同的,所以next[j]在这里:
j的位置匹配不上了,所以j移动到next[j]:
然后继续,但是如果又匹配不上了,那就继续移动到next[j],(蓝色部分相同):
然后继续……
匹配的部分就这样完美地实现了!!,代码如下:
void find(){
int j=0;
for(int i=0;i<n;i++){
while(j && s[i]!=t[j]) j=nex[j];
if (s[i]==t[j]) j++;
if (j==m) ans[++num]=i-m+2;
}
return;
}
BUT,我们还要求next数组,其实是一样的,无非就是子串自己和自己的匹配
上面已近说过了,next[j]其实就是j前的字符串最长的相同前缀和后缀的长度,我们可以这样求:
比方说我们已经求完了next[i],现在要求next[i+1],目前的情况是这样的:
橙色部分是相同的,我们先让j=next[i]
如果这时,t[i]==t[j],那就美滋滋了,next[i+1]=j+1
但是如果不一样(有人说:从头来! 那请问与暴力有神马区别?),看,由于橙色部分是相等的,所以如果这是我们调取一下next[j],会发现这四段蓝的全相同:
如果不看中间的部分:
我们会发现问题与刚才的一样了(只不过这里的next[j]相当于刚才的j,打代码时,我们把j跳到next[j])
这时如果t[next[j]]=t[i],那我们也可以得到t[i+1]=next[j]+1
如果不匹配——有和刚才一样找到next[next[j]],再次重复……
void getnex(){
int j=0;
nex[0]=nex[1]=0;
for (int i=1;i<m;i++){
while(j && t[i]!=t[j]) j=nex[j];
nex[i+1]=t[i]==t[j]?++j:0;
}
}
OK,完事
如果你还知道一个东西叫字典树的话(不知道,戳)
字典树是妈妈,KMP是爸爸,会有一个小孩纸叫做AC自动机(注意!不是自动AC机,他们有本质区别)
戳我学习自动AC AC自动机
他是一个用KMP的想法在trie树上实现的算法
88
于HG机房