KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。
KMP字符串匹配算法,得到next数组o(n),匹配o(n+m)
int Next[N];
char mo[N];
char str[N];
void Get_next()
{
int len=strlen(mo);
int j=-1;
Next[0]=-1;
int i=0;
while(i<len)
{
while(j!=-1&&mo[i]!=mo[j])
j=Next[j];
Next[++i]=++j;
}
}
//这种预处理模式串的方法更快
void Get_nextval()
{
int len = strlen(mo);
int i,j;
j = Next[0] = -1;
i = 0;
while(i<len)
{
while(j!=-1&&mo[i]!=mo[j])
j = Next[j];
if(mo[++i] == mo[++j])
Next[i]=Next[j];
else
Next[i]=j;
}
}
//返回模式串T在主串S中首次出现的位置
//返回的位置是从0开始的。
int KMP_Index()
{
int len = strlen(str);
int molen = strlen(mo);
int i = 0, j = 0;
while(i < len&& j < molen)
{
if(j == -1 || str[i] == mo[j])
{
i++; j++;
}
else
j = Next[j];
}
if(j == molen)
return i - molen;
else
return -1;
}
//返回模式串在主串S中出现的次数
int Kmp_count()
{
int len1=strlen(str);
int len2=strlen(mo);
int ans=0;
int i=0,j=0;
while(i<len1)
{
while(str[i]!=mo[j]&&j!=-1)
j=Next[j];
i++;
j++;
if(j>=len2)
{
ans++;
j=Next[j];
}
}
return ans;
}
//循环节问题
//重要的性质len-next[i]为此字符串的最小循环节(i为字符串的结尾),即循环周期
//另外如果len%(len-next[i])==0&&len!=(len-next[i]),此字符串的循环次数为len/(len-next[i])的循环串;
void Is_Cycle()
{
//示例代码,求最少补几个字符串得到>=2的循环串
int len = strlen(mo);
int length = len - Next[len]; //最小循环节长度
if(len%length==0&&length!=len) //刚好循环完,不必再补元素
printf("0\n");
else
printf("%d\n",length-len%length); //未循环完补length-(len%length)个元素
}
KMP算法,核心在于怎样得到next数组。
next数组的含义就是一个固定字符串的最长前缀和最长后缀相同的长度。
在某次匹配失败的时候,即str[i]!=mo[j]的时候,我们就直接将j变成next[j]的位置,为什么可以这样变,因为能匹配到这个位置,说明p1p2...p(j-1) = s(i-j+1)s(i-j+2)...s(i-1),如果我们找到最长的最长前缀和最长后缀相同的长度k,即p1p2...pk = p(j-k+1)p(j-k+2)...p(j-1),那么我们的s(i-k+1)s(i-k+2)...s(i-1) = p1p2...pk-1了,也就匹配成功了,只需将j指针回朔,而i指针不变来降低复杂度了。
这里写了两种得到next数组的方法,第一种Get_next()就是直接按上面那种方法得来的,如果不等就一直回溯,否则next[++i]=++j。
然而仍然有缺陷,比如模式串aaaab和匹配串aaabaaaab按照上面得到底next数组为next[]={-1,0,1,2,3},在i=3的时候(从0开始),j会回溯成2,1,0,三次变化,然而这都是不需要的。因为他们都是相同的字符,所以在这里匹配不成功,回溯到next[j]的位置仍然不成功。所以就有了第二种改进一点的算法,如果回溯位置字符相同,则直接next[i]=next[j],就可以跳过当前位置了。