1、KMP简介:
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。字符串的模式匹配实质上就是寻找模式串P是否在主串T 中,且其出现的位置。我们对字符串匹配的效率的要求越来越高, 应不断地改良模式匹配算法,减少其时间复杂度。可以应用于网络信息查找、文本中字符串的索引等。
2、KMP算法介绍:
(以下为个人理解,不保证所有人都理解我的学习想法,如果看不懂next优化及创建过程的文章末尾有详解链接)
已知两个字符串S,T;S为匹配串,T为模式串,要在S中找到是否有T,步骤如下:
假设现在S串匹配到 i 的位置,T串匹配到 j 的位置; 如果当前字符匹配成功(S[i]=T[j]);则继续匹配下一个字符 ;(i++,j++) 如果当前字符匹配失败(S[i]!=T[j]);将模式串T的 j 标记移动到j-next[j]位继续匹配,i 标记位置不用改变;
(比较于暴力匹配引出next数组)对于KMP算法的核心就是制造next数组;可以使得暴力匹配提高效率:举一个例子:
如果S串为"aaaaaaab",T串为"aaaab",我们以暴力匹配的做法,在i=4,j=4时S[4]='a',T[4]='b',此时S[5]!=J[5];我们就需要将i,j标记向后移动一位再次去匹配,但事实上,由于T串的特殊性即T[0-3]='a',在第一次匹配中,S和T中前四个字符都相等了(‘a’),其实没必要再去反复的比较S[1~3]与T[0~3]的字符了。因此,我们就需要一个next数组来标记当S与T不匹配时,应该把T中的哪一个元素继续与i比较。
3、next[ ]:
对于每一个模式串我们会事先计算出模式串的内部匹配信息,在匹配失败时最大的移动模式串,以减少匹配次数。在已经匹配的模式串子串中,找出最长的相同的前缀和后缀,然后移动使它们重叠,我们举一个一般性的例子,假设T='abaabcaba';
在T中我们要找到当前字符T[j]的最大相等的前缀与后缀,长度为(k);
模式串 | a | b | a | a | b | c | a | b | a |
最大前后缀相等元素长度: | 0 | 0 | 1 | 1 | 2 | 0 | 1 | 2 | 3 |
例如:T[8] = 'a';它的前缀是T[0-2]='aba',后缀是T[6-8]='aba',结果为3;
但是对于最长的相同的前缀和后缀,我们要清楚在S与T不匹配时,标记J具体要移动多少,所以通过上面找最长的相同的前缀和后缀的方式,我们创建next数组。
模式串T | a | b | a | a | b | c | a | b | a |
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
next[j] | -1 | 0 | 0 | 1 | 1 | 2 | 0 | 1 | 2 |
next数组的创建:
#define imax 105
int next[imax];
void getnext(char *p)
{
int lenp=strlen(p);
next[0]=-1;
int k=-1;
int j=0;
while(j<lenp-1)
{
if(k==-1||p[j]==p[k]) //找到前缀最长匹配度
{
j++;
k++;
if(p[j]!=p[k]) //如果当前元素在前缀中不存在时,
{
next[j]=k;
}
else
next[j]=next[k]; //KMP优化
}
else
{
k=next[k];
}
}
return;
}
4、匹配过程:
还是以上面为例,如果S串为"aaaaaaab",T串为"aaaab",那么我们知道next[ ]数组应该为{-1,0,1,2,3};在i<strlen(S),j<strlen(T)的情况下,我们比较S与T,当i=4,j=4时;S[4]!=T[4];我们按照算法i不变,j=next[j] = 3,比较:S[4]=T[3]='a',S[5]!=T[4],j=next[j]=3; S[5]=T[3]='a',S[6]!=T[4],j=next[j]=3; S[6]=T[3]='a',S[7]=T[4]='b'; j=strlen(T);完成。也就是说在i从5-7时,一直在比较S中是否有‘ab’。匹配成功时,我们要求返回位置。
KMP匹配:
int KMP(char *s,char *p)
{
int i=0;
int j=0;
int lens=strlen(s);
int lenp=strlen(p);
while(i<lens&&j<lenp)
{
//如果j=-1(表示第一次开始或者重新开始匹配),即失配于首字符
if(j==-1||s[i]==p[j])
{
j++;
i++;
}
else
{
//如果j!+-1,或者当前匹配失败 ,则
j=next[j]; // i不变,j=next[j]
}
}
if(j==lenp)
return i-j;//匹配成功 返回位置
else
return -1;
}
5、测试:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main()
{
int t,i;
char a[10005],b[10005];
scanf("%d",&t);
while(t--)
{
scanf("%s",a);
scanf("%s",b);
if(strlen(a)>=strlen(b))
{
getnext(b);
if(KMP(a,b)>-1)
printf("yes:%d\n",KMP(a,b));
else
printf("no:%d\n",KMP(a,b));
}
else
{
getnext(a);
//getnext(b);
if(KMP(a,b)>-1)
printf("yes:%d\n",KMP(a,b));
else
printf("no:%d\n",KMP(a,b));
}
for(i=0;i<strlen(b);i++)
{
printf("%d ",next[i]);
}
}
return 0;
}
附上一篇精致的解析KMP博客: