KMP定位子串
给定两个字符串S和B,定位B在S中的位置称之为串的模式匹配,其中S称之为主串,T为模式串。
1.朴素的模式匹配算法
一种很基础朴素的方法就是暴力匹配,采用定长顺序存储结构,不依赖于串的操作进行暴力匹配。
算法思想 :采用暴力匹配的方法,对主串和模式串设置i和j两个指针,从左到右一个一个进行匹配,当出现不匹配时,主串指针回溯,模式串从新开始进行匹配。
初始处理:
进行匹配:
指针回溯:
指针回溯:
代码如下
int Index(String S,String T){
int i = 1,j = 1;
while(i <= S.len && j <= T.len){
if(S.ch[i] == T.ch[i]){ // 匹配则指针向前移动
++i;
++j;
}
else{
i = i - j + 2; //指针回溯
j = 1;
}
}
if(j > T.len)return i - T.len; //匹配成功返回第一个定位
return 0;
}
时间复杂度分析:分析最坏时间复杂度,如果主串前部分一直和模式串不能匹配,则指针会一直进行回溯,直到进行到最后的串,成功匹配,此时的时间复杂度取决于主串S和模式串T的长度n和m。时间复杂度为
2.KMP
很显然如果采用暴力算法,当主串和模式串不长的情况下,时间复杂度还算可行,但是一旦长度很长,时间开销将会很大。于是三位大牛:D.E.Knuth、J.H.Morris和V.R.Prat一起发明了KMP算法,致力于解决这个主串指针回溯的问题来降低时间开销。KMP算法相对难度有点大,一开始学习半知不解,参考了一些博主的博文总结了一下这个算法。KMP算法详解-彻底清楚了(转载+部分原创) - sofu6 - 博客园 (cnblogs.com)
2.1 算法思想
算法思想:算法思想是按照我个人理解,KMP的核心就是基于朴素匹配算法,解决其主串指针回溯,从而降低时间开销。
过程描述对于S和T的匹配,当有相同部分,则指针将会向前移动,当出现不匹配时,是否有必要进行回溯呢?答案当然是否,因为对于主串来说,对于匹配的部分是已知信息,能否根据已知信息来判断模式串指针的下一步移动呢,从而达到降低时间复杂度的效果。由此,主串指针是不需要移动的,只需要根据已知信息来判断模式串的指针移动即可。 通过如下图片来感受下KMP的简化:
从上图发现当移动到C和D时,不匹配,肉眼观察,可以直接将j移动成如下的情况:
为什么呢?因为肉眼观察,前面的ABA是匹配的鸭!
从上述的过程分析,我们可以发现当匹配失败时,j要移动的下一个位置k。存在着这样的性质:最前面的k个字符和j之前的最后k个字符是一样的。使用数学公式进行如下描述:
使用如下图片进行理解:
通过上述的分析,我们可以发现,进行匹配的过程中,对于主串的指针,是一往无前,无需回溯的,算法的核心在对于模式串的指针移动,当移动到不匹配的位置时候,需要进行计算,确定指针j的下一步走向。
2.2 next数组
接下来就是重点,我们现在知道了对于算法的核心就是当出现不匹配时候,我们的指针j要进行移动,那么j如何移动呢?根据模式串的特性,我们分析,这个移动跟主串无关,只跟模式串有关,我们需要一个next数组,来装载模式串不同位置上,对于出现不匹配时的指针下一步移动处理。next数组的重要意义就是,当出现不匹配时,模式串指针根据所在位置,进行下一步移动处理。下面介绍几种求解next数组的方法。