kmp
kmp匹配主要匹配的是两个字符串,我们将需要匹配的记录为s1,用于匹配的串为s2.
kmp匹配的是从以当前s1中的ch字符为末尾的字符串能够在s2中匹配的最大前缀的长度。
也即是s1中的每个前缀的最长后缀所匹配的s2的最长前缀。
先考虑自身与自身匹配的情况:
如果用dp+数学归纳来思考:
假设当前需要匹配j位置的字符,从0—j-1位置都已经记录了该位置能够匹配到的自身字符串的真最大前缀长度,用next数组记录了该长度。
首先要获取next[j-1]的大小,表示不考虑j位置字符的匹配长度,现在再加入字符时,需要查看next[j-1]+1位置的字符s2[next[j-1]+1]==s2[j],如果想等,则由定义可知,next[j]=next[j-1]+1,即长度比j-1位置的长度大1。如果不相等,我们应该去寻找0—next[j-1],这一段字符串中以j-1位置字符为末尾的最长匹配长度,其实就是和寻找j一样的过程,是个递归。也即是next[next[j-1]],去查看是否和s2[j]相同,不然继续向下处理。
int Next[maxn]; //指向的是坐标 不是长度,因此没有匹配为-1
void getNext(char *s)
{
int len=strlen(s);
Next[0]=-1;
for(int i=1;i<len;i++)
{
int fail=Next[i-1];
while(fail!=-1 && s[fail+1]!=s[i]) //fail不是根,并且不相等就向前匹配
fail=Next[fail];
//匹配完,说明要么到-1根的位置,或者匹配上了。
//检查一下是否匹配上,匹配上了就+1
if(s[fail+1]==s[i])
fail++;
Next[i]=fail;
}
}
同理,两个不同字符串之间的kmp也是一样的思路。
int kmp[maxn];
void getkmp(char *s1,char *s2) //用s2去匹配s1
{
int len1=strlen(s1);
int len2=strlen(s2);
getNext(s2);
int fail=-1;
for(int i=0;i<len1;i++)
{
while(fail!=-1 && s1[i]!=s2[fail+1])
fail=Next[fail];
if(s1[i]==s2[fail+1])
fail++;
kmp[i]=fail;
//当前表示s1已经匹配上的s2中的(位置坐标)。如果匹配到了len2,则可以进行下面其他的处理(根据题意)
if(kmp[i]==len2-1)
fail=-1;
}
}
两个代码进行合并:
int Next[maxn]; //指向的是坐标 不是长度,因此没有匹配为-1
int kmp[maxn];
void getNext(char *s)
{
int len=strlen(s);
Next[0]=-1;
for(int i=1;i<len;i++)
{
int fail=Next[i-1];
while(fail!=-1 && s[fail+1]!=s[i]) //fail不是根,并且不相等就向前匹配
fail=Next[fail];
//匹配完,说明要么到-1根的位置,或者匹配上了。
//检查一下是否匹配上,匹配上了就+1
if(s[fail+1]==s[i])
fail++;
Next[i]=fail;
}
}
void getkmp(char *s1,char *s2) //用s2去匹配s1
{
int len1=strlen(s1);
int len2=strlen(s2);
getNext(s2);
int fail=-1;
for(int i=0;i<len1;i++)
{
while(fail!=-1 && s1[i]!=s2[fail+1])
fail=Next[fail];
if(s1[i]==s2[fail+1])
fail++;
kmp[i]=fail;
//当前表示s1已经匹配上的s2中的(位置坐标)。如果匹配到了len2,则可以进行下面其他的处理(根据题意)
if(kmp[i]==len2-1)
fail=-1;
}
}
应用
1.求循环节
由于Next的匹配机制,会递归向下进行匹配,因此我们可以得到一个字符串的循环节。循环节由字符串最后一位的失陪指针指向位置到该字符位置的长度。即len-1-Next[len-1]。
int Next[maxn]; //指向的是坐标 不是长度,因此没有匹配为-1
void getNext(char *s)
{
int len=strlen(s);
Next[0]=-1;
for(int i=1;i<len;i++)
{
int fail=Next[i-1];
while(fail!=-1 && s[fail+1]!=s[i]) //fail不是根,并且不相等就向前匹配
fail=Next[fail];
//匹配完,说明要么到-1根的位置,或者匹配上了。
//检查一下是否匹配上,匹配上了就+1
if(s[fail+1]==s[i])
fail++;
Next[i]=fail;
}
}
int getXunHuanJie(char* s)
{
int len=strlen(s);
getNext(s);
return len-1-Next[len-1];
}
扩展kmp
扩展kmp匹配主要匹配的是两个字符串,我们将需要匹配的记录为s1,用于匹配的串为s2.
扩展kmp匹配的是从以当前s1中的ch字符为首的字符串所组成的后缀能够在s2中匹配的最大前缀的长度。
也即是s1中的每个后缀的最长前缀所匹配的s2的最长前缀。
比kmp算法慢一点
int Next[maxn];
int ex[maxn];
void get_next(char *str)
{
int len=strlen(str);
Next[0]=len;Next[1]=0;
while(str[Next[1]]==str[Next[1]+1] && Next[1]+1<len)Next[1]++;
int p=1;
for(int i=2;i<len;i++)
{
if(i+Next[i-p]<p+Next[p])Next[i]=Next[i-p];
else
{
Next[i]=max(p+Next[p]-i,0);
while(i+Next[i]<len&&str[i+Next[i]]==str[Next[i]])Next[i]++;
p=i;
}
}
}
void exkmp(char *s1,char *s2)
{
get_next(s2);
int len1=strlen(s1),len2=strlen(s2);
ex[0]=0;
while(ex[0]<len1&&ex[0]<len2 && s1[ex[0]]==s2[ex[0]])ex[0]++;
int p=0;
for(int i=1;i<len1;i++)
{
if(ex[p]+p>i+Next[i-p])ex[i]=Next[i-p];
else
{
ex[i]=max(ex[p]+p-i,0);
while(ex[i]<len1&&ex[i]<len2&&s1[i+ex[i]]==s2[ex[i]])ex[i]++;
p=i;
}
}
}