KMP 算法
简介: KMP算法是一种能够在线性时间内求出一个串在另一个串的所有匹配位置.
核心: KMP算法中失配数组 F[ i ] 的求解. F[i] 数组表示主串S中以第i个字符(S[i-1])为尾的后缀与模式串T的前缀的最长公共部分.
KMP本质其实是看主串S中每个i位置的后缀最多能与T的多长的前缀匹配的算法. 也就是前缀匹配后缀的算法. 进行模式匹配只是一个附加的结果. 比如说:F[i] ==x 表示i位置的模式串T的0到i-1字符串的后缀能与T的0到x-1匹配. 也就是说当T[i] 与 S[j] 不匹配的时候, 就取用T[F[i]] == T[x] 与S[j] 匹配看看是否匹配.
KMP这种前缀后缀的思想要好好体会~ 是学习后面的字符串算法的基础!
注意: 蓝书上给的是MP算法 均摊复杂度是线性的. KMP需要失配指针一次性指到最前面, 而不是MP中的一次一次的跳(类似于并查集的路径压缩).
/*************************MP模板****************************/
char T[1000];//待匹配串
char P[100];//模板串
//失配指针,记住这里f要比P多一位
//因为P到m-1即可,但是f还要计算出m的失配指针
int f[101];
void getFail(char *P, int *f)
{
int m = strlen(P);
f[0] = f[1] = 0;
for(int i = 1; i < m; i++)//虽然字符串是0到m-1,但是要求出f[m]的值
{
int j = f[i];
while(j && P[i] != P[j]) j = f[j];
f[i + 1] = P[i] == P[j] ? j + 1 : 0;
}
}
void find(char *T, char *P, int *f) //找到所有匹配点
{
int n = strlen(T);
int m = strlen(P);
int j = 0;
for(int i = 0; i < n; i++)
{
while(j && T[i] != P[j]) j = f[j];
if(T[i] == P[j]) j++;
if(j == m) printf("%d\n", i - m + 1);//就算j到m了,也用f[m]继续匹配
}
}
/*************************MP模板****************************/
/*************************KMP模板****************************/
char T[1000];//待匹配串
char P[100];//模板串
int f[101];//优化后的失配指针,记住这里f要比P多一位,因为P到m-1即可,但是f还要计算出m的失配指针
int f2[101];//f2用来保存KM指针,是为优化f的失配指针,f保存的是优化之后的失配指针
void getFail(char *P, int *f)
{
int m = strlen(P);
f[0] = f[1] = 0;
f2[0]=f2[1]=0;
for(int i = 1; i < m; i++)
{
int j = f2[i];
while(j && P[i] != P[j] ) j = f2[j];
f2[i+1] = f[i + 1] = (P[i] == P[j]) ? j + 1 : 0;
//既然i+1的失配位置指向j+1,但是P[i+1]和P[j+1]的内容是相同的
//所以就算指针从i+1跳到j+1去,还是不能匹配,所以f[i+1]直接=f[j+1]
if(f[i+1]==j+1 && P[i+1]==P[j+1]) f[i+1]=f[j+1];
}
}
void find(char *T, char *P, int *f) //找到所有匹配点
{
int n = strlen(T);
int m = strlen(P);
int j = 0;
for(int i = 0; i < n; i++)
{
while(j && T[i] != P[j]) j = f[j];
if(T[i] == P[j]) j++;
if(j == m) printf("%d\n", i - m + 1);
}
}
/*************************KMP模板****************************/