(部分分析灵感及样例来源于 @xehoth)
复习模板的时候打了几遍,比当时学的时候感觉要清晰一些,于是抱着KMP在屏幕上滚来滚去滚来滚去滚来滚去~
呐,首先对于几乎所有第一次接触KMP的人来说的那个大坑(喂就是那个神奇的next[ ]数组),我们先按下不表。
假设模式串是 abcieruabc ,目标串是 abc
abcieruabc
---
abc
第一次匹配,匹配成功,如果不用KMP算法
abcieruabc
|
abc
第一个就不匹配,若是继续用这种朴素的方法,下一次还是第一个就不匹配。(等等我们换一个更具特色的样例)
ttittittyyios
-----|
ttitty
呐,如果用朴素的方法
ttittittyyios
-|
ttitty
再然后
ttittittyyios
|
ttitty
。。。。我们还是谈谈KMP好啦。
由于目标串中”tt”重复了两次,则第二次匹配完全可以从第一次第二个”tt”所在的位置开始匹配。
当然,从某一个方面来说’t’也重复了几次,但是
ttittittyyios
-|
ttitty
就匹配失败了,所以要从最大重复的字串入手。
放道题来讨论吧。
字符串匹配【KMP模板】
给定两个由小写字母构成的字符串 L 和 S 。
请你从左到右,找出子串 L 在母串 S 中每次出现的开始位置(匹配位置)。
输入格式
第一行:给一个全由小写字母构成的母串 S(0<S的长度≤1000000);
第二行:给一个全由小写字母构成的子串 L(0<L的长度≤S的长度)。
输出格式
按升序输出一行一个整数,分别表示子串 L 在母串 S 中每次出现的开始位置。
如果子串 L 在母串 S 中没有出现,则输出“NO”。
样例数据 1
输入
yuabcierabcde
abc
输出
3
9
样例数据 2
输入
abcdefg
abcdefu
输出
NO
备注
【样例1说明】
从第3个位置起,第一次匹配;
从第9个位置起,第二次匹配。
for(int i=2,j=0;i<=m;i++)
{
while(j!=0&&t[i]!=t[j+1])
j=next[j];
if(t[i]==t[j+1]);
j++;
next[i]=j;
}
这就是对目标串t的一个初始判定
其实后来二者匹配的时候代码差不多,因为第一个其实可以看做是目标串自己和自己匹配。只是第一次是不要求是否匹配完全,只要求找出是否存在可以匹配的来降低复杂度与时间;第二次就需要讨论一下 j==m 的情况,及是否匹配完全,若完全,将标记 f 进行修改,输出此时最后一个位置i减去目标串的长度m再加1,即此时目标串首位的位置。最终若f作了修改,,则继续寻找下一个匹配点;若没有,输出”NO”,再找下一个匹配点。
bool f=1;
for(int i=1,j=0;i<=n;i++)
{
while(j!=0&&s[i]!=t[j+1])
j=next[j];
if(s[i]==t[j+1])
j++;
if(j==m)
{
cout<<i-m+1<<endl;
j=next[m],f=0;
}
}
if(f==1)
cout<<"NO"<<endl;
那么大体上算法如何实现的已经说完了,接下来浅显简短地说一下那个神奇的next[ ]数组。(明明就是自己也不怎么懂吧。。。。人艰不拆穿)
据说是
next 数组就是模式串前缀的最长公共前后缀的长度。
唔那大概是这样的
ttitty 前缀 后缀 next
1 t - - 0
2 tt t t 1
3 tti t,tt ti,i 0
4 ttit t,tt,tti tit it t 1
5 ttitt …… …… ……
6 ttitty …… …… ……
(部分分析灵感及样例来源于 @xehoth)
说了那么多,还有完整版无水代码。
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
using namespace std;
int n,m;
int next[1000010];
char s[1000010],t[1000010];
int main()
{
scanf("%s%s",s+1,t+1);
n=strlen(s+1),m=strlen(t+1);
for(int i=2,j=0;i<=m;i++)
{
while(j!=0&&t[i]!=t[j+1])
j=next[j];
if(t[i]==t[j+1])
j++;
next[i]=j;
}
bool f=1;
for(int i=1,j=0;i<=n;i++)
{
while(j!=0&&s[i]!=t[j+1])
j=next[j];
if(s[i]==t[j+1])
j++;
if(j==m)
{
cout<<i-m+1<<endl;
j=next[m],f=0;
}
}
if(f==1)
cout<<"NO"<<endl;
return 0;
}
喵呜~
(部分分析灵感及样例来源于 @xehoth)
来自2017.10.10
——我认为return 0,是一个时代的终结。