码迷,mamicode.com
首页 > 其他好文 > 详细

8.2 kmp 扩展kmp

时间:2018-08-02 23:05:47      阅读:242      评论:0      收藏:0      [点我收藏+]

标签:scan   next数组   detail   space   字符串   数组   分享   pre   kmp   

假设一母串S,子串P

KMP:用于求解子串P在母串S中第一次出现的位置,或是在母串S中出现的次数。(最长公共前缀后缀)

next数组的含义:next[i]表示前面长度为i的子串中,前缀和后缀相等的最大长度。

 

 

拓展kmp是对KMP算法的扩展,它解决如下问题:(最长公共前缀)

定义母串S,和子串T,设S的长度为n,T的长度为m,求T与S的每一个后缀的最长公共前缀,也就是说,设extend数组,extend[i]表示T与S[i,n-1]的最长公共前缀,要求出所有extend[i](0<=i<n)。

注意到,如果有一个位置extend[i]=m,则表示T在S中出现,而且是在位置i出现,这就是标准的KMP问题,所以说拓展kmp是对KMP算法的扩展,所以一般将它称为扩展KMP算法。

next数组的含义:next[i]表示子串T与T[i,m-1](T的第i个后缀)的最长公共前缀(与上面的定义类似,就是以子串代母串)

 

KMP求最小循环节、循环周期:https://blog.csdn.net/hao_zong_yin/article/details/77455285

定理:假设S的长度为len,则S存在最小循环节,循环节的长度L为len-next[len],子串为S[0…len-next[len]-1]。

(1)如果len可以被len - next[len]整除,则表明字符串S可以完全由循环节循环组成,循环周期T=len/L。

(2)如果不能,说明还需要再添加几个字母才能补全。需要补的个数是循环个数L-len%L=L-(len-L)%L=L-next[len]%L,L=len-next[len]。

定理可以这么理解:

对于一个字符串,如abcd abcd abcd,由长度为4的字符串abcd重复3次得到,那么必然有原字符串的前八位等于后八位。

也就是说,对于某个字符串S,长度为len,由长度为L的字符串s重复R次得到,当R≥2时必然有S[0..len-L-1]=S[L..len-1],字符串下标从0开始

那么对于KMP算法来说,就有next[len]=len-L。此时L肯定已经是最小的了(因为next的值是前缀和后缀相等的最大长度,即len-L是最大的,那么在len已经确定的情况下,L是最小的)。

 

 

A题:

Description

 HHM要给自己的小狗起名字,他是这样做的,他先找到一个字符串S,字符串S所有前缀与后缀相同的字串,都可以作为名字。现在他想知道所有符合条件的字串的长度。

Input

有多组输入,每一组输入包含一个字符串S(1<=length of S <=400000)

Output

输出所有可能的子串长度,中间以空格结束。最后一个数字后不要有空格。

Sample Input

ababcababababcabab
aaaaa

Sample Output

2 4 9 18
1 2 3 4 5

HINT

 

此题就是求母串S的公共前缀后缀值(跟子串没关系)

设len=strlen(S),next[len]等于长度为len的子串(母串S本身)的最长公共前缀后缀值,所以枚举长度从1到next[len]的前缀后缀,如果相等就记录长度即可。

技术分享图片
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
char str[maxn*4];
int Next[400010];
void getnext(char p[])
{
    int plen=strlen(p);
    Next[0]=-1;
    int j=0,k=-1;
    while(j<plen)
    {       //p[k]表示前缀,p[j]表示后缀
        if(k==-1||p[j]==p[k])
        {
            j++;
            k++;
            //if(p[j]==p[k])k=next[k];//因为不能出现p[j]=p[next[j]],所以当出现时需要继续递归,k = next[k] = next[next[k]]
            Next[j]=k;
        }
        else k=Next[k];
    }
}
int main()
{
    while(~scanf("%s",str))
    {
        getnext(str);
        int l=strlen(str);
        int m=Next[l];
        int i,j;
        string a="",b="";
        for(i=0,j=l-1;i<m;i++,j--)
        {
            a=a+str[i];
            b=str[j]+b;
            if(a==b)printf("%d ",i+1);
        }
        printf("%d\n",l);
    }
    return 0;
}
View Code

 

B题:

Description

HHM碰到了这样一个问题,给出一个字符串,问它最多由多少相同的子串组成

Input

每个测试数据输入一个字符串s,s(1<=len(s)<=1000000)。输入以.结束

Output

输出最多有多少个相同的子串组成。

Sample Input

abcd
aaaa
ababab
.

Sample Output

1
4
3

HINT

 

这题就是一个求循环节的题目,相同的子串即为母串的循环节

用len-next[len]求出循环节的长度

用len/(len-next[len])即可得到有几个相同的子串

技术分享图片
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
char str[maxn];
int next[maxn];
void getnext(char *s,int next[])
{
    int plen=strlen(s);
    next[0]=-1;
    int j=0,k=-1;
    while(j<=plen-1)
    {       //p[k]表示前缀,p[j]表示后缀
        if(k==-1||s[j]==s[k])
        {
            j++;
            k++;
            //if(p[j]==p[k])k=next[k];//因为不能出现p[j]=p[next[j]],所以当出现时需要继续递归,k = next[k] = next[next[k]]
            next[j]=k;
        }
        else k=next[k];
    }
}
int main()
{
    while(1)
    {
        scanf("%s",str);
        memset(next,0,sizeof next);
        if(str[0]==.)break;
        getnext(str,next);
        int l=strlen(str);
        int ans=1;
        if(l%(l-next[l])==0)//不整除就没有循环节
        {
            ans=l/(l-next[l]);
        }
        printf("%d\n",ans);
    }
    return 0;
}
View Code

 

8.2 kmp 扩展kmp

标签:scan   next数组   detail   space   字符串   数组   分享   pre   kmp   

原文地址:https://www.cnblogs.com/raincle/p/9409782.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!