kmp算法是一种字符串的匹配算法,平常我们写一个字符串的匹配算法是非常容易的,所谓的字符匹配,就是说当已知一个主串S,并且有一个字符串T,我们看看T这个字符串是否在S串中出现过,当然说的有些白话了~~,我看了许多关于kmp的资料,网上有很多,但很长很长的时间的一段时间里,都没有消化。心里非常内疚,其实到现在,我都不确定自己是否真的理解了kmp。但时间一长,就萌生了把各个kmp资料整理一下的冲动,下面就是我整理的,或许你看了之后还不明白,这正常~~
首先,我先说一下普通的字符串的匹配算法,我相信,这样的算法一个正常的ACMer都会写出来,并且如果S很短,用这个算法就行了,就是一一比较,若出现不相等就回去重新比,假如S[i-j....i]与T[0....j]都成功了!我们必然要比较i+1和j+1,若不相等,则就要从头比较(这也是最浪费时间的)也就是比较S[i-j+1]与T[0],废话不多说给出代码~~~
注:S[m...n]表示所以m到n区间内的字符;
代码
int Index_BF(char S[],char T[],int pos)
{
int i=pos;//从pos位置开始比较,一般初始化为0
int j=0;
while(S[i+j] != '\0' && T[j] !='\0')
if(S[i+j]==T[j])
j++;//匹配则继续匹配
else
{
i++;
j=0;//不匹配就要重新开始匹配
}
if(T[j]=='\0')
return i;//匹配成功 返回下标
else
return -1;//说明匹配成功
}
但是,如果S这个字符串很长呢?那么匹配就要花费很长时间,我们先来看看我们这个kmp是如何提高匹配的效率的或者说它是如何工作的:首先我们还是给出S和T我们的目的是看看T是否在S中出现过,需要两个“指针” i,j。我们比较是S[i-j...i]与T[0,j]肯定完全相等,如果S[i+1]与T[j+1]还相等,我们不多说,肯定要i++,j++;这和普通的匹配没什么不同,但如果不相等即:S[i+1]与T[j+1]失配了,我们怎么办?kmp算法的思想是调整j的值(减小j值),假设改完后的j为j'那么,这个j'会保持
S[i-j'...i]与T[0...j']匹配,只不过字符数量减小了,但不至于和上面那个普通匹配算法一样已经匹配的字符数量为0。但问题又来了,我们如何去改变j的值,j的值肯定是越大越好。这就需要我们的next函数,其实它就是一个一维数组,里面的值就间接或直接告诉我们该怎么对j进行处理。现在我证明的问题是这个next数组各个值为什么是正确的,也就是说我们怎么保证S[i-j'..i]和T[0...j']是匹配的?(我认为这个是最重要的)
我们先说一下next取值,next的下标索引对应着T字符串的数组的索引,例如next[4]=3,就说明T[4]对应的next函数值为3,如果S[m]与T[4]不匹配,我们就要利用next[4]进行调整。正规next的取值有4种类型(我们假设S[m]和T[n]不相等了~~)
1.next[n]=-1 这就是说S[m]和T[0]比较过,相等,因此直接比较S[m+1]和T[0]即可。
2.next[n]=0 表示让你直接比较T[0]和S[m]即可。
3.next[n]=k>0但k<n,表示S[m]的前k个字符与T开始的k个字符已经比较过了现在比较T[k]与S[m]是否相等。
我在给出next是怎样求出来的?(next 的值对应了T的每个字符)
<1>next[0] = -1 我们规定next[0]一定为-1
<1>next[j] = -1 表示模式串T下标为j的字符,如果T[j]==T[0],(如果T[j]!=T[0],则next[j]肯定不等于-1);并且j的前面的k个字符与开头的k个字符不同(1<=k<j)或者相同但是T[k]==T[j];
<3>next[j]=k 表示 模式串T的下标为j的字符T[j]如果j前面的k个字符与开头的k个字符相等并且T[k]!=T[j]则next[j]=k;例如:T="abcdabcd" 则next[7]=3因为前面3个字符(abc)和j=7前面3个字符(abc)相等并且T[6]!=T[3];
再例如T="abababc" 则next[6]=4 因为开头的4个字符(abab)与j=6前面的4个字符(abab)相等,并且T[6]!=T[4].
<4>除了上述的情况next[j]=0;
最后我们根据next的值就能确定实际要回溯的位置了,怎样得到T的next函数代码如下:
void get_nextval(char*T)
{
int j=0,k=-1;
next[0]=-1;
while(T[j]!='\0')
{
if(k==-1||T[j]==T[k])
{
++j;
++k;
if(T[j] != T[k])
next[j]=k;
else
next[j]=next[k];
}
else
k=next[k];
}
}
另一种得到next的写法:
void getNext(char *pattern,char*next)
{
next[0]=-1;
int k=-1,j=0;
while(pattern[j] != '\0')
{
if(k != -1 && pattern[k]!=pattern[j])
k=next[k];
++j;
++k;
if(pattern[k]==pattern[j])
next[j]=next[k];
else
next[j]=k;
}
}
于是kmp的算法模板
int KMP(char *Text,char *Pattern)const
{
if(!Text||!Pattern!!Pattern[0]=='\0'||Text[0]=='\0')
return -1;
int len=0;
char*c=Pattern;
while(*c++!='\n')
++len;
int *next=new int [len+1];
getNext(Pattern,next);//得到Pattern的next函数值
int index=0,i=0,j=0;
while(Text[i]!='\0' &&Pattern[j]!='0')
{
if(Text[i]==Pattern[j])
{
i++;
j++;
}
else
{
if(next[j]!=-1)
j=next[j];//即使为零也没关系
else
{
j=0;
i++;
}
index=i;
//如果到这说明又要重新匹配了,因此要保留刚开始匹配的地方
}
}
delete []next;
if(Pattern[j]=='\0')
return index;//匹配成功
else
return -1;
}
如果还是不太理解,我建议做两个纸带一个代表S一个代表T然后手动移动一下。。