最近在网上学习算法 发现大家热聊的一个话题 就是KMP 很多说这个算法很费解。但我却觉得这个算法是因为大家都只关注其实现的公式 而不去注重其逻辑,下面我介绍我所谓的逻辑 看能否让你明白
字符串匹配T与字符串S之间在匹配不成功时 存在什么关系呢?
假设在匹配串T的p处出现了不匹配现象,我们需要重新调整字符串S的位置让其与T重新匹配,对吗?
那现在有两种情况出现,一种情况就是p之前的字符串T[0]到T[p-1]可以移动m个点让其与T[0]开始的点匹配
另一种情况这个m不存在。不存在m则说明S字符只能从j这个点上跟T[0]开始比较,因为它无法移动m个点去覆盖从T[0]开始的字符串T.
所以这个算法的核心就是去寻找m,怎么去找m呢,很简单:
1.T是以T[0]为起点的字符串,那么你可以从T[1]开始寻找m,当遇到下一个T[0]时,说明这个m可能存在了,那你还要去比较T[1]与T[m+1]是否相等.
2.根据1,当你找到某个点T[m+k]开始与T[k]不匹配了,那么这个点就是告诉你从T[0]到T[k-1]这个短字符串与之后的字符从T[m]到T[m+k-1]的字符串是匹配的.但在T[m+k]这个点上就不再匹配了,也就是过了这个点T[k+m+1]之后的点是无法在移动m就能与T[0]开始的字符串匹配了,这个时候你需要的就是重新寻找m1.所以这个算法的关键就是不断去寻找m1而已,重新寻找m1需要将T[m+k+1]与T[0]开始的点比较。
3.在没寻找m的点T[j],我们可以这样说,如果在这个位置与S不匹配,那么S移动需要从S[i]开始与T[0]重新匹配,因为根据上面的情况2可知.
所以我们不妨把不存在的点。
那么下面就是介绍算法为什么这么写的时候了?
我们知道如果不存在,那么S不匹配的这个点与T[0]开始比较,我们不妨设这种状态为0。
当找到可能的m,也就是找到一个字符开始与T[0]相等,那么与T[0]相等的这个点可能从T[0],...,T[K]都与T[m],...,T[m+k]都相等,在比较的过程中我们用K++表示有K个已经比较相等了
那么从T[m]到T[m+k]我们也设为状态0,因为这些点上如果不匹配,即使在T[j]这个点之前的点,你可以移动m让其匹配上,但因为S与匹配后的字符串的点T[j]不相等,所以S一样只能从S[i]重新与T[0]比较.
好了,再来看看最后一种情况让遇到点T[m+k+1]开始与T[k+1]不匹配的这个关键点吧,开始这个不相等的点,我们可以记他的值为K+1,这是为什么呢?
因为开始不匹配这个点之前的字符与从T[0]开始到T[k]匹配上了,当从T[k+1]!=T[j]而S[i]!=T[j]那么说明移动了m个点后S可能从S[i]开始与T[k+1]相匹配.
接着我们又开始寻找下一个m1点,从T[m+k+1]处与T[0]开始比较.不相等的全置为0,除非k1等于之前的m点.因为如果k1等于之前的m点则从T[m+k+k1+1]必定继承了之前
T[m+k]的特点.因为该处必定存在T[m+k+1]到T[m+k+k1]包含了短字符串T[0]到T[m+k],那么对于T[m+k+k1+1]不匹配的点可以与T[k]进行比较。
除了m点其他点都为0故可以判断如果T[j]等于T[k]必定有T[j]的匹配值为T[k].
找到m1之后,我们可以得到T[m1+k1]=k1,但是这个时候出现了一种情况就是T[k1]可能是之前的T[m+k]的点,那这个时候说明什么?
这说明在新的T[m1]-T[m1+k1]的短字符串中包含了从T[m]-T[m+k]字符串,那么对于S串与T[m]-T[m1+k1]匹配了,那么S字符串就与T[m]-T[m+k]相匹配了.
因为T[m1+k1+1]与T[k1+1]不匹配了,并且也知道T[k1+1]就是以前的m点,那就有T[k1+1]!=T[m+k+1],则T[m1+k1+1]有可能会等于T[m+k+1].
如果等于 我们要比较T[m1+k1+1]...与T[k+1+1]...是否相等直到找到下一个点m2.
如果不等于则需要m2需要从头来也就是从T[0]开始与T[m1+k1+1]进行比较.
由此类推.
这种算法的重点就是寻找m,并且找到m后需看与T[j]不匹配的点是否为之前的m点,是的话在比较之前的k值是否与现在不匹配的点相等,不相等又要继续判断
不匹配的点是否为之前的m点...知道不是m点位置.则为0使得T[j]重新与T[0]比较。
因为我们知道对于m点必然存在其匹配值不为0,其他的点匹配值都为0。所以我们在判断是否为m点时可直接
while(1){
j++;k++;
if(T[j]==T[k])
T[j]=T[k];
else{
T[j]=k;
do{
k=T[k]
}while(k==0||T[k]==T[j])
}
}
至此 我们列出了T的所有情况并记录了两种状态0和1,但是在算法的时候,我们发现了我们在寻找m的时候不断得重新从T[0]开始比较也就是说这个与T[0]比较相等的点我们可以-1来表示,这说明了S[i]!=T[0]也就是说S要从下一个点i+1开始与T[0]比较了.
依据上面的情况 我写出如下算法计算状态:
void get_next(const char*T,int next[]){
int j=0,k=0;
next[0]=0;
while(T[j++]!='\0'){
if(T[j]==T[k]){
T[j]=T[k];
k++;
}else{
T[j]=k;
do{
k=T[k]
}while(k==0||T[k]==T[j]) }
}
这就是只记录了两种状态的匹配值,是不是很简单,但是XX发现一直在比较中肯定会出现K=0,那么他希望把与T[k=0]的这个点比较相等的点的状态记录-1,那么这个算法
就变成了这样
void get_nextval(const char *T, int next[])
{
int j = 0, k = -1;
next[0] = -1;
while ( T[j] != '\0' )
{
if (k == -1 || T[j] == T[k]) //(k=-1表明是第一个点或者与重新寻找m时候找到与T[0]相等的点了||j自然是不断得++与T[k]比较而已,只不过在需要重新找m时候k重新赋值)
{
++j; ++k; //(这个好理解j自然是不断得加1因为它无需回溯,但是k呢?为何也要+1,因为m出现后我们需要记录比较后相等了几个字符另外当需要开始寻找m的时候
if (T[j]!=T[k]) //
next[j] = k; //只要不相等则确定j的匹配值,这个时候我们发现当这个条件退出后,while就会一直去重新计算K了吧
else
next[j] = next[k]; //对于我上面的程序只有两种状态很好理解,但是这里多了一种状态-1,它是否具有继承性呢?
}
else
k = next[k]; //重新计算k值以便重新寻找m
}
奇怪了吧,一下子 复杂了,其实你看我注释的部分就知道他为何这么写了.
对于状态-1表明的是该点与T[0]相等并且在每次比较与T[0]相等的点必定是另一个循环体的开始部分其具备T[0]开始的循环中T[0]的匹配值。