KMP
KMP实现
//求一个字符串在另一个字符串出现的第一个位置
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
int* NextImprove(char *P)
{
int m=strlen(P),j=0; //主串指针
int* N=(int*)malloc(m*sizeof(int)); //Next表
int t=N[0]=-1; //P[-1]为通配符
while(j<m-1)
{
if(t<0 || P[j]==P[t])
{
j++;
t++;
N[j]=P[j]!=P[t]?t:N[t]; //改进版
}
else
t=N[t];
}
return N;
}
//next构建思路(递推结合画图):next[j+1]<=next[j]+1
//当且仅当P[j]=P[next[j]]是取等号,即j对应的字符与next[j]对应的字符相同
//对next[j]的理解:即是在P[0,j)中,最大自匹配的真前缀和真后缀的长度
//next[j]即是最大自匹配的真前缀和真后缀的长度,也是比对失败时应该跳转到的位置
int* BuildNext(char *P) //未改进版
{
int m=strlen(P),j=0; //主串指针
int* N=(int*)malloc(m*sizeof(int)); //Next表
int t=N[0]=-1; //P[-1]为通配符
while(j<m-1) //因为根据m-1就可以算出m的值,所以只循环到m-1
{
if(t<0 || P[j]==P[t])
N[++j]=++t;
else
t=N[t];
}
return N;
}
int match(char* P,char* T) //KMP主算法 P为模式串,T为为文本
{
int* Next=BuildNext(P);
int n=strlen(T),i=0;
int m=strlen(P),j=0;
while(i<n && j<m)
{
if(j<0 || P[j]==T[i])
{
i++;
j++;
}
else
j=Next[j];
}
return i-j;
}
int main()
{
char P[100]="myj";
char T[100]="hahamyj";
int len1=strlen(P);
int len2=strlen(T);
int Position=match(P,T);
if(Position<=len2-len1) //len1 和len2 位字符串的个数,如果返回的值超过len1-len2说明P不在T中
printf("%d\\n",Position);
else
printf("未出现\\n");
}
KMP打表
/*
求循环周期
打表题:用快速幂求出前几百的结果,找规律。发现在42时结果是循环的。
把前42的结果放入数组
知识点:快速求幂
*/
/*
KMP求最小循环节:
若知道字符串为循环字符串
对于next数组中的 i, 符合 i % ( i - next[i] ) == 0 && next[i] != 0 , 则说明字符串循环
而且循环节长度为: i - next[i]
循环次数为: i / ( i - next[i] )
若i % ( i - next[i] )!=0 说明需要再添加几个字母才能补全
需要补的个数是循环节长度-余数
参考:
<https://www.cnblogs.com/jackge/archive/2013/01/05/2846006.html>
讲解:<https://segmentfault.com/a/1190000008575379>
题目:poj 3461、poj 2752、poj 2406、poj1961
代码参考:<https://blog.csdn.net/guhaiteng/article/details/52108690>
补:扩展KMP
*/
# include <stdio.h>
# include <math.h>
#include <stdlib.h>
#include <string.h>
int tmp;
int BuildNext(char *P) //未改进版
{
int m=strlen(P),j=0; //主串指针
int* N=(int*)malloc(m*sizeof(int)); //Next表
int t=N[0]=-1; //P[-1]为通配符
while(j<m) //因为根据m-1就可以算出m的值,所以只循环到m-1
{
if(t<0 || P[j]==P[t])
{
N[++j]=++t;
if(j%(j-N[j])==0 && N[j] != 0) //求循环最小节条件,比如12341234123 求得循环点为4.因为是循环点,所以循环次数至少为2才能求的出来
{
tmp=j-N[j];
return tmp;
}
}
else
t=N[t];
}
return -1;
}
long long pow_mod(long long a,long long i,long long n)
{
if(i==0) return 1%n;
int temp=pow_mod(a,i>>1,n);
temp=temp*temp%n;
if(i&1) temp=(long long)temp*a%n;
return temp;
}
int main()
{
long long i,j;
long long sum=0;
char A[3000];
A[0]='0'; //注意,要把今天也算进去
int m;
for (i=1; i < 300; i++)
{
A[i]=pow_mod(i,i,7)+'0';
}
BuildNext(A);
printf("%d\\n",tmp);
return 0;
}
KMP匹配串次数
//一个字符串在另外一个字符串出现的次数
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
int* BuildNext(char *P) //未改进版
{
int m=strlen(P),j=0; //主串指针
int* N=(int*)malloc(m*sizeof(int)); //Next表
int t=N[0]=-1; //P[-1]为通配符
while(j<m) //把m-1改为了m,是为了算N[m],算出这个串整体的最大公共前缀和后缀。
//假想法:假想P[m]添加一个跟任何字符都匹配失败的字符,然后把添加后的P正常使用KMP就好。只是,在j==m时,sum++
{
if(t<0 || P[j]==P[t])
N[++j]=++t;
else
t=N[t];
}
return N;
}
void match(char* P,char* T)
{
int sum=0;
int* Next=BuildNext(P);
int n=strlen(T),i=0;
int m=strlen(P),j=0;
while(i<n)
{
if(j<0 || P[j]==T[i])
{
i++;
j++;
}
else
j=Next[j];
if(j>=m) //当j==m说明字符串与母串匹配成功。假想P[m]有一个跟任何字符都匹配失败的字符,
//于是把模式串进行与普通失败时同样的移动,也就是j=Next[j],
{
sum++;
j=Next[j];
}
}
printf("%d\\n",sum);
return;
}
int main()
{
char P[10010];
char T[1000010];
int N;
scanf("%d",&N);
while(N--)
{
scanf("%s",P);
scanf("%s",T);
int len1=strlen(P);
int len2=strlen(T);
match(P,T);
}
}
字符串所有公共前缀后缀
/*
POJ 2752
给一个字符串,求它的所有公共前缀后缀的长度(其中包括了最大公共前缀后缀)
*/
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
int* BuildNext(char *P) //未改进版
{
int m=strlen(P),j=0; //主串指针
int* N=(int*)malloc(m*sizeof(int)); //Next表
int t=N[0]=-1; //P[-1]为通配符
while(j<m) //把m-1改为了m,是为了算N[m],算出这个串整体的最大公共前缀和后缀。
//假想法:假想P[m]添加一个跟任何字符都匹配失败的字符,然后把添加后的P正常使用KMP就好。只是,在j==m时,sum++
{
if(t<0 || P[j]==P[t])
N[++j]=++t;
else
t=N[t];
}
return N;
}
int main()
{
char P[10010];
while(scanf("%s",P)!=EOF)
{
int len1=strlen(P);
BuildNext(P);
while(P[len1]!=0)
{
printf("%d\\n",P[len1]);
len1=P[len1];
}
}
}
BM
- 区别:KMP:善于利用经验 BM:乐于借鉴教训
- 单次匹配概率越小,BM性能越明显(字母表越大,匹配概率越小)
- BM_GC(好后缀策略):最好n/m,最坏n*m