KMP算法的经典例题(poj 3461、poj 2752、poj 2406、poj1961)

传送门:POJ-3461

最简单的KMP题,找出第一个字符串在第二个字符串中出现次数。

#include <iostream>
#include <cstdio>
#include <cstring>
#define Memset(x, a) memset(x, a, sizeof(x))
using  namespace std;
const int N=1e6+10;
char  w[N],t[N];
int next[N];
int sum;

void  getNext(const char P[],int next[]){
  int  m=strlen(P);
  int i=0,j;
  j=next[0]=-1;
  while(i<m){
    while(-1!=j && P[i]!=P[j])j=next[j];
    next[++i]=++j;
  }
}

void  kmp(const char T[],const char P[],int next[]){
  int n=strlen(T),m=strlen(P);
  int i,j;
  getNext(P,next);
  i=j=0;
  while(i<n){
    while(-1!=j && T[i]!=P[j])j=next[j];
    i++;j++;
    if(j>=m){
      sum++;
      j=next[j];//这儿修改很重要,不然会超时
    }
  }
}

int main(){
  int T;
  scanf("%d",&T);
  while(T--){
    sum=0;
    Memset(next,0);
    scanf("%s%s",w,t);
    kmp(t,w,next);
    printf("%d\n",sum);
  }
  return 0;
}

传送门:POJ-2752

思路:其实是next数组的使用

下面给出描述: (i>1)[下标从0开始] 
next[i]的意义就是:前面长度为i的字串的【前缀和后缀的最大匹配长度】 

那么这题怎么利用这个性质呢? 

详细分析一下:【就用上面的第一个例子说明吧】 

求出next值:[非修正]
下标:      0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17

串:          a  b  a  b  c  a  b  a  b  a  b   a   b   c   a   b   a   b

next值:  -1  0  0  1  2  0  1  2  3  4  3   4   3   4   5   6   7   8   9


len2 = 18    next[len2] = 9 
说明对于前面长度为18的字符串,【长度为9的前缀】和【长度为9的后缀】是匹配的, 即上图的蓝色跟红色匹配 
也就是整个串的最大前后缀匹配长度就是9了 
所以接下来根本不需要考虑长度大于9的情况啦 

好了!既然现在只需考虑长度小于9的前后缀匹配情况,那么 
[问题就转化成蓝色串的前缀红色串的后缀的匹配问题了!!! 
又因为蓝串==红串 
所以问题又转化成 
找蓝串自己的前缀跟自己的后缀的最大匹配了!!! 
那么我们现在就要找next[9]的值了【next[9]的含义:蓝串的最大前后缀匹配】 

回忆第一步:我们找的是next[len2]=9【len2=18】 
怎么使得第二部目标变成9【求next[9]】呢? 
其实next[len2]=9同时可以表示为:最大匹配前后缀的【前缀长度】 
那么next[9]的意义就是: 
【主串】的最大匹配前后缀的【前缀】的【最大匹配前后缀】了!! 
也就是上面蓝串的前后缀最大匹配长度了!! 

那么算法描述就是: 
第一步:求next[len2], 即next[18] = 9; 
第二步:把9代进来,即求next[9] = 4; 
第三步:把4代进来,即求next[4] = 2; 
第四步:next[2] = 0; 也就是下标2之前的串已经没有前后缀可以匹配了 
所以答案就是: 2 4 9 18 【PS: 从小到大输出,18是串长,显然符合题意】 

#include <iostream>
#include <cstdio>
#include <cstring>
#define Memset(x, a) memset(x, a, sizeof(x))
using  namespace std;
const int N=4e5+10;
int  next[N],ans[N];
char  s[N];

void  getNext(const char P[],int next[]){
  int  m=strlen(P);
  int i=0,j;
  j=next[0]=-1;
  while(i<m){
    while(-1!=j && P[i]!=P[j])j=next[j];
    next[++i]=++j;
  }
}

int main(){
  while(~scanf("%s",s)){
    Memset(next,0);
    getNext(s,next);
    int cnt=0;
    int len=strlen(s);
    int j=next[len];
    while(j>0){
      ans[++cnt]=j;
      j=next[j];
    }
    for(int i=cnt; i>0; i--)printf("%d ",ans[i]);
    printf("%d\n",len);
  }
  return 0;
}

传送门:POJ-2406 Power Strings

思路:KMP,next表示模式串如果第i位(设str[0]为第0位)与文本串第j位不匹配则要回到第next[i]位继续与文本串第j位匹配。则模式串第1位到next[n]与模式串第n-next[n]位到n位是匹配的。所以思路和上面一样,如果n%(n-next[n])==0,则存在重复连续子串,长度为n-next[n]。

例如:a    b    a    b    a    b

next:-1   0    0    1    2    3    4

next[n]==4,代表着,前缀abab与后缀abab相等的最长长度,这说明,ab这两个字母为一个循环节,长度=n-next[n];

#include <iostream>
#include <cstdio>
#include <cstring>
#define Memset(x, a) memset(x, a, sizeof(x))
using  namespace std;
const int N=1e6+10;
int next[N];
char  s[N];

void  getNext(const char P[],int next[]){
  int  m=strlen(P);
  int i=0,j;
  j=next[0]=-1;
  while(i<m){
    while(-1!=j && P[i]!=P[j])j=next[j];
    next[++i]=++j;
  }
}

int main(){
  while(~scanf("%s",s)){
    if(s[0]=='.')break;
    Memset(next,0);
    getNext(s,next);
    int  len=strlen(s);
    if(len%(len-next[len])==0)printf("%d\n",len/(len-next[len]));
    else printf("1\n");
  }
  return 0;
}

传送门:POJ-1961 Period

题意:

给你一个字符串,求这个字符串到第i个字符为止的循环节的次数。

比如aabaabaabaab,长度为12.到第二个a时,a出现2次,输出2.到第二个b时,aab出现了2次,输出2.到第三个b时,aab出现3次,输出3.到第四个b时,aab出现4次,输出4.


思路:

这道题就是上题的加强版而已。上一道题输出一个字符串的循环节出现的次数,这个是到第i个字符为止,其实就是多了一层循环。把这个字符串遍历一次即可,具体思路也以参考小白书的例题。

#include <iostream>
#include <cstdio>
#include <cstring>
#define Memset(x, a) memset(x, a, sizeof(x))
using  namespace std;
const int N=1e6+10;
char s[N];
int next[N];
int n;

void  getNext(const char P[],int next[]){
  int  m=strlen(P);
  int i=0,j;
  j=next[0]=-1;
  while(i<m){
    while(-1!=j && P[i]!=P[j])j=next[j];
    next[++i]=++j;
  }
}

int main(){
  int kase=0;
  while(~scanf("%d",&n)&&n){
    scanf("%s",s);
    Memset(next,0);
    getNext(s,next);
    printf("Test case #%d\n",++kase);
    for(int i=2; i<=n; i++){
      if(next[i]>0&&i%(i-next[i])==0)printf("%d %d\n",i,i/(i-next[i]));
    }
    printf("\n");
  }
  return 0;
}


    原文作者:KMP算法
    原文地址: https://blog.csdn.net/guhaiteng/article/details/52108690
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞