参考的是:http://www.matrix67.com/blog/archives/115
kmp算法就是解决两个字符串,目标文本T,关键词字符串S,匹配的问题。
比如 T:abababcdabc S:abc 在T里找出连续的子字符串是S的位置
先看看完全暴力的算法:
<span style="font-size:18px;">void violent(const char *T,const char *S)//将完全匹配的子字符串的起始位置存入position[]数组中
{
int lenT=strlen(T);
int lenS=strlen(S);
tot=0;
for(int i=0;i<lenT;i++)
{
int j;
for(j=0;j<lenS;j++)
if(T[i+j]!=S[j])
break;
if(j==lenS)position[tot++]=i;
}
}
</span>
就是枚举T里面的起始位置,再一步一步和S匹配啦。
当然其中的 i<lenT 可以优化为 i<lenT-lenS+1 最坏的复杂度:O((lenT-lenS+1)*lenS)
复杂度有点高,然后Knuth、Morris、Pratt三个人就提出了KMP算法。
在暴力算法中,有的字符比较是没有意义的(手动模拟即会发现)
例如:
T:ababc S:aba
当i=1,j=0 时不用比较就会知道他们是不可能匹配的
KMP算法就是利用减少这些无效的比较来降低复杂度的。
看看KMP的流程
T:ababca S:baba
将 T 看成是固定的。
不断向右移动 S 观察是否匹配
当匹配到
T: a b a b | c a
S: b a b | a
i=1,j=3时 未匹配 (这里的 i 的含义是 将S的首部移动到了 T 中 T[i] 的位置)
KMP算法直接转移到了
T: a b a b | c a
S: b | a b a
跳过了i=2的步骤,为什么可以跳?因为已经比过了。当i=1,j=1时T[i+j]==S[j] ,T[ i+j=2 ]已经比过了!,S直接向右滑行,直到T[2]再次匹配。这里可能产生了误区,再看一个例子:
T: b a b a b | c a
S: b a b a b | d
下一步可直接变成
T: b a b a b | c a
S: b a b | a b d
将S向右滑行 直到 | 左边的S子字符串和 T中 | 左边的完全匹配,用表达式来描述就是:
当T[i-1]=S[j]且T[i]!=S[j+1]时,j=len ,S[k]=S[j-k],0<=k<=len,且len!=j,取j=max(len);发现 j 的下一个取值只与S相关,预处理即可,将对于每一个j ,j的下一个取值存入P[]中
运行部分
void KMP(const char *T,const char *S)
{
int lenT=strlen(T);
int lenS=strlen(S);
KMP_solveS(S,P);//当字符串S多次使用时,此函数应放在外面
tot=0;
int i,j=-1;//假设T[-1]=S[-1],只是个假设....
for(int i=0;i<lenT;i++)
{
while(j>=0&&S[j+1]!=T[i])
j=P[j];
if(S[j+1]==T[i])
j=j+1;
if(j==lenS-1)
{
position[tot++]=i-lenS+1;
j=P[j];//可能有多个匹配
}
}
}
O(lenT)的,那个while()在整个函数里总的运行次数最多为lenT
当然喽,i<lenT ,也可以优化为 i-1+lenS-j-1<lenT
预处理部分
void KMP_solveS(const char *S,int *P)//实质是自我匹配的过程,该函数将S的信息存放在P中
{
int lenS=strlen(S);
P[0]=-1;
int j=-1;
for(int i=1;i<lenS;i++)
{
while(j>=0&&S[j+1]!=S[i])
j=P[j];
if(S[j+1]==S[i])
j=j+1;
P[i]=j;
}
}
和上面的是类似的 O(lenS)
总的复杂度:O(lenS+lenT)
给个总的
<span style="font-size:18px;">/*
KMP算法 : 解决字符串匹配问题
*/
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int const MAXN=1000+10;
char S[MAXN];
char T[MAXN];//目标文本
int position[MAXN],tot;//记录所有完全匹配的初始位置
//暴力测试函数
void violent(const char *T,const char *S)
{
int lenT=strlen(T);
int lenS=strlen(S);
tot=0;
for(int i=0;i<lenT-lenS+1;i++)//i<lenT
{
int j;
for(j=0;j<lenS;j++)
if(T[i+j]!=S[j])
break;
if(j==lenS)position[tot++]=i;
}
}
int P[MAXN];//P[i] 存的是对于 字符串S S[k]=S[i-len+k],0<=k<=len,存在len使所有的k均满足等式,且取P[i]=最大的len;若不存在则P[i]=-1;
void KMP_solveS(const char *S,int *P)//实质是自我匹配的过程,该函数将S的信息存放在P中
{
int lenS=strlen(S);
P[0]=-1;
int j=-1;
for(int i=1;i<lenS;i++)
{
while(j>=0&&S[j+1]!=S[i])
j=P[j];
if(S[j+1]==S[i])
j=j+1;
P[i]=j;
}
}
void KMP(const char *T,const char *S)
{
int lenT=strlen(T);
int lenS=strlen(S);
KMP_solveS(S,P);//当字符串S多次使用时,此函数应放在外面
tot=0;
int i,j=-1;
for(int i=0;i-1+lenS-j-1<lenT;i++)//i<lenT
{
while(j>=0&&S[j+1]!=T[i])
j=P[j];
if(S[j+1]==T[i])
j=j+1;
if(j==lenS-1)
{
position[tot++]=i-lenS+1;
j=P[j];
}
}
}
int main()
{
//T:目标文本 S:关键字 文本
while(scanf("%s%s",T,S)!=EOF)
{
printf("after violent\n");
violent(T,S);
for(int i=0;i<tot;i++)
printf("%d ",position[i]);
printf("\n\n");
printf("after my kmp\n");
KMP(T,S);
for(int i=0;i<tot;i++)
printf("%d ",position[i]);
printf("\n\n");
}
return 0;
}
初学kmp,欢迎各位指正