KMP算法
-
画了一晚上和一上午时间研究了一下KMP算法
-
该算法主要功能是匹配字符串,由于传统匹配方法主串存在指针回溯问题,针对此进行改进。
-
先研究子串性质
-
首先,字串与主串匹配时第一个不同的字符我们叫当前位置,《大话数据结构》举了两个例子,很有启发性。如下图
-
对于本例当前位置为6,上图是传统匹配步骤,但是由于a不同于bcde,但bcde又分别与主串匹配,所以a没有必要再去匹配主串的这些位置,直接跳到第6步就ok,拿主串的j=1来跟当前位置对应的主串位置匹配即可。这是一种情况,再看下例:
-
对于本例,当前位置是j=6,上图是传统匹配步骤,由于当前位置左侧ab重复出现一次,由于子串中已知ab=ab,ab又与主串匹配,因此不需要再像图4、5那样匹配一遍,加上上例讲的情况,可以直接跳到第6步匹配,ab直接到重复的下一个位置,匹配j=3处的字符即可。(图上好像有点错误,第一幅图错了,应该跟后几幅图一样)
-
-
总结起来,就是看当前位置-1长度字串前后缀的相似度(重合度),如何判断此重合度?使用next[]数组。
-
next[j]数组是各个当前位置(1-j)对应的j(例如j=3)的变化,可以说知道了next[]数组,就可以直接跳过中间步骤,使子串的前缀重合上主串的后缀,比较next[j]位置的字串。
-
理解了这些之后,下面附上KMP算法程序
- ```C
void get_next(Srtring T,int *next){
int i=1;//后缀最后一个字符指针
int j=0;//回退到j=0,说明没有重复字串,特殊情况取1;前缀的最后一个字符指针
while(i<T[0]){
if(j==0||T[i]==T[j]){//回退到初始位置,或者字符匹配时,进入。
i++;//如果当前字符匹配,说明i+1的当前位置,对应有j个重复字串,
j++;
next[i]=j;//若i为当前位置,则跳转到j;
}else{//若不匹配,则回退
//可以这样理解,对当前字符串的前缀后缀进行匹配,之前一直匹配,直到这个位置不匹配了,说明当前位置为j,根据next[]数组的用法,j应该跳转为next[j]位置再次跟i处字符匹配。即
j=next[j];
}
}
}
其实next数组的计算之所以难,就难在那句话j=next[j],但这句话就是在求next数组时用到了next数组的。理解好next数组的原理和思想,这句话自然就看懂了。
程序运行如下图
-
其中7、8步发生了回退。
-
边界条件:j=0.若发生j=next[1]=0;说明了此时前缀的第一个字符都与后缀的最后一个字符不匹配,说明没有重复字串,其他情况,next[j]取1.更新i,更新后缀最后一个字符继续匹配。
-
如果T[i]=T[j],如果前缀最后一个字符与后缀最后一个字符匹配,好了,next[i+1]=j+1;即可。
-
求取完next[]数组即可写匹配程序
-
int Index_Kmp (String s,String T,int pos){ int i=pos;//从主串pos位置开始 int j=1;//字串指针 int next[255];//next数组 get_next(T,next);//获取next数组 while(i<=S[0]&&j<=T[0]){//若i小于S的长度,j小于T的长度 if(j==0||S[i]==T[j]){//j如果回退为0,说明第一个字符都跟当前的i无法匹配,跳过。 i++;//i++ j++;//j++,指向子串第一个字符 }else{ j=next[j];//从当前位置回退 } } if(j>T[0])//当j大于T[0]时,说明完全匹配 return i-T[0];//返回主串中与子串匹配的串的首字符位置 else return 0;//不匹配。 }
-
其实整个KMP步骤,就是找当前位置(不匹配位置),找到之后根据next数组回退,拿回退的字符再跟当前字符比较,成功则继续往下匹配,重新找下一个当前位置,不成功继续回退,直到回退到边界j=0,跳过此字符。
-
看算法窍门:首先要把程序翻译成自然语言,通过例子,弄清楚程序的变量表示数目,程序的步骤是什么。我用的例子是主串bacbababadababacambabacaddababacasdsd,子串ababaca,你就对着程序匹配上述字符串,自然就明白程序原理了。
-
翻译成自然语言之后,就容易理解了,其实没那么复杂,很巧妙。