/*
介绍kmp算法前,不妨先了解这样一个数组:next数组
next[i]的值是 取字符串S的前i个字母组成的字符串s 的"最长公共前后缀"的长度
先解释前缀与后缀的含义:字符串"abcd"有三个前缀:a,ab,abc
有三个后缀:bcd,cd,d
例如,有字符串S=“aabaaaf”,
i=1时, s=“a”, 没有前后缀, next[1]=0;
i=2时, s=“aa”, 前缀"a"与后缀"a"公共长度为1, next[2]=1;
i=3时, s=“aab”, 前缀"a"或"aa"与后缀"b"公共长度均为0, next[3]=0;
i=4时, s=“aaba”, 前缀、后缀均取"a"时,公共前后缀的长度最长, next[4]=1;
i=5时, s=“aabaa”, 前缀、后缀均取"aa"时,公共前后缀的长度最长, next[5]=2;
…
那么,这个next数组有什么用呢?
假设有两个字符串,
如果用传统的匹配算法,'d’与’f’不匹配(前面都正确匹配),那么s字符串向右移动一位:
'b’与’a’不匹配,s字符串再向右移动一位:
就这样一直移动,直到完全匹配。
显然,这种匹配算法容易理解但很耗时。
我们仔细观察这两个字符串:
S字符串与s字符串的第六个字母不匹配,也就是它们的前五个字母相同,
而对于s字符串的next数组,next[5]=2,
也就是说,s字符串的第一、二个字母与第四、五个字母相同,
而s字符串的第四、五个字母又与S字符串的第四、五个字母相同,
那么,s字符串的第一、二个字母必然与S字符串的第四、五个字母相同!!!
所以,当S字符串与s字符串的第六个字母不匹配时,我们不必一次只移动一位,
可以直接移动到如下位置(s字符串的第一、二个字母与S字符串的第四、五个字母相对应):
事实上,当S[i]!=s[j]时,令j=next[j],继续比较S[i]与s[j],可以达到一次移动多位的效果。
(因为next[i]是 取字符串前i位组成的新字符串 的最长公共前后缀的长度,
那么next[i]就是这个前缀的下个位置的下标
例如aabaa,公共前后缀长度最长时,
前缀、后缀均为"aa",2是next[5]的值,同时是前缀"aa"下一个位置’b’的下标)
例如上例中,S[6]!=s[6](i=6,j=6),令j=next[j]( j = 2 ),这时比较的是S[6]与s[2]。
现在,我们已经感受到了next数组的“神通广大”,那么问题来了,如何求next数组?
显然:next[0]没有意义,而next[1]=0
对于i>1,如果s[i]==s[next[i]],那么s[i]=next[i]+1。
证明:s=“aabaaba”,i=5时,next[5]=2,说明前5个字母组成的字符串有长度为2的公共前后缀。如果该前缀的下一个字母与该后缀的下一个字母相同,那么:next[6]=next[5]+1=3
但是,如果s[i]!=s[next[i]]呢?(个人认为,这里是kmp算法中最难理解的地方)
假如有s=“aabbaaa”,i=6时,next[6]=2,s[6]!=s[next[6]],
但是,我们可以看到,s[5]==s[0],如果s[6]==s[1],那么next[7]=next[1]+1。
那么,怎么利用s[5]==s[0]这个条件呢?当然是利用我们的next数组。
假设有字符串s,next[i]=m,s[i]!=s[next[i]]。
由next[i]=m可以推出:由s字符串的前i个字母组成的字符串,存在长度为m的相同的前缀和后缀,
我们设该前缀和后缀分别为s1和s2,假如next[m]=k,
那么,由s字符串的前m个字母组成的字符串(也就是s1),存在长度为k的相同的前缀和后缀,
我们设该前、后缀分别为s11和s12,由于s1与s2相同,在s2同样可以找到s21与s22,
使得s21与s22长度为k且相同。事实上,s11=s12=s21=s22。
我们根据s11=s22可知:s[i]前面的k个字母(也就是s22),
与s字符串的前k个字母(也就是s11)相同,所以,如果s[i]==s[k],那么next[i+1]=k+1。
也就是说:假如j=next[i],如果s[i]!=s[j],我们可以令j=next[j],继续比较s[i]与s[j],
如果相等,next[i+1]=j+1,
如果不等,令j=next[j],一直循环直至j<=0 (j<=0时next[j]没有意义)
得到next数组之后,就是S与s的匹配,这与求next数组的思路差不多,大家看代码自行体会。
*/
#include<stdio.h>
#include<string.h>
const int maxn=20;
int next[maxn];
void getNext(char *s){
int len=strlen(s);
int j=0;
for(int i=1;i<len;i++){
while(j>0&&s[i]!=s[j])
j=next[j];
if(s[i]==s[j])
j++;
next[i+1]=j;
}
}
int kmp(char *S,char *s){
int len1=strlen(S);
int len2=strlen(s);
int j=0;
for(int i=0;i<len1;i++){
while(j>0&&S[i]!=s[j])
j=next[j];
if(S[i]==s[j])
j++;
if(j==len2){
return i-j+1;//如果匹配成功,返回第一个匹配位置
}
}
return -1;//匹配不成功返回-1
}
int main(){
char s[maxn],S[maxn];
while(scanf("%s%s",S,s)!=EOF){
memset(next,0,sizeof(next));;
getNext(s);
int ans=kmp(S,s);
printf("%d\n",ans);
}
return 0;
}
运行结果:
ABCDEFG EF
4
ABCDEFG EE
-1