KMP算法是一种基于字符串的数据结构中的一种模式串匹配算法,同时也是数据结构考研算法难度中排名前三的一种算法
BF算法:
在说到KMP算法之前我们需要先引入一个更基础的算法--BF(朴素匹配)算法
举例:假设现在有一主串S:a b a a c a a b c a b a a b c
有一模式串T:a b a a b c
要求在主串S中找到与模式串相同(匹配)的字串
初始化两枚指针i=1,j=1,它们分别指向了主串S里的第一位元素,以及模式串T中的第一位元素。若S.[i]=T.[j],则i++;j++依次后推直到j>T.Length,则认为主串S的某一子串与模式串完全匹配,返回i-T.length,即返回S中和T相匹配的字串的第一个元素的位序。若S.[i]!=T.[j](即主串中的字串中的某一个元素与模式串中所对应的那个元素不匹配)则需要将主串中的指针指向下一个字串的第一个位置,然后再将模式串中的指针移动重置到第一位,换算成算法就是:i=i-j+2;j=1。
BF匹配算法的代码展示:
int Index(SString S,SString T){
int i=1,j=1;
SString ch;
while(i<=S.length&&j<=T,length){
if(S.ch[i]=T.ch[j]){
i++,j++;
}
else{
i=i-j+2//主串指针i指向下一个待比较的字串的第一个位置
j=1;//模式串指针j重置为1
}
if(j>T.length)
return i-T.length
else
return 0;
}
这里不难发现朴素算法每匹配一次都需要将指针回溯,这样就会造成时间复杂度指数级升高,造成效率低下。于是大洋彼岸的三位老哥,K某,M某,P某在BF算法的基础上进行了改良,造就了KMP算法。
KMP算法的基础逻辑:
KMP算法基于BF进行了改良,使得主串的指针不用回溯,只用回溯模式串的指针,从而极大提升了算法的执行效率,然而模式串指针的回溯需要先将next数组求出,通过next数组我们可以知道,当模式串的第n个字符不适配时,我们应该将模式串的指针回溯到第几个位置
tip:不匹配的字符之前肯定是相匹配的
特殊情况:当模式串的第一个指针不适配时,我们先要将模式串的指针赋0,然后再将模式串和主串的指针分别向后挪动一位
if(T.ch[0]!=S.ch[0]) j=0,j++,i++;
举例:求出模式串abaabc的next数组:
具体方法可以参考另一位老哥的文章:如何求next数组
next数组的进一步优化:
当我们求出next数组之后,不难发现,当j指针指向3时,如果不匹配,那么j指针就会跳转到a这个位置,但是由于当j指针指向3时,所匹配的字符肯定是和a不相匹配的,如此以来,j指针就没必要跳转到a这个位置(也就是next[3]=1是冗余的),我们可以直接让它指向再后一个步骤的位置(next[3]=next[1]=0),同理当j指针指向5时,next[5]=1,从而的进一步优化成为nextval数组。
举例匹配字符串:a b a a c a a b c a b a a b c(此处演示使用的是next数组)
KMP算法的代码演示:(优化next数组后的方案即替换next[]为nextval[]即可)
int index_kmp(SSteing,TString,next[]){
int i=1,j=1;
while(i<S.length&&j<T.leng){
if(j==0||S.ch[i]=T.ch[j]){
++j,++i;
}
else
{
j=next[j];
}
if(j>T.length){
return i-T.length;//匹配成功,返回指针所指向的字串的第一个字符的位置
else
return 0;
}
其实不难发现,KMP算法相较于BF算法,改动的地方并不大,无非就是使得j指针回溯满足了next数组的规律,以及j指针回溯时让i指针保持了原有的位置。
总结:
要使用KMP算法,最关键的步骤是去求得next数组,然后通过next数组来引导j指针的下一步应该指向的模式串位置,i指针的位置不会产生回溯,从而大幅度的提高了字符串匹配的效率,以及很大程度的下降了字符串匹配算法的时间复杂度。