个人感受
经历字符串匹配很多次了,可是每一次都需要回顾一遍kmp算法,回顾完以后,始终处在似懂非懂的边缘,所有这次记录下来,希望对我的理解有所帮助
背景
原解法
首先,如果用传统的方式去匹配子串是这样的:
int Match(char *s1, int len1, char *s2, int len2){
//判断s2是否是s1的子串,是则返回第一次匹配的位置
if(len1 < len2)return -1;//子串长度大于主串
int i = 0, j = 0;
while(i < len1){
if(s1[i] == s2[j]){
i ++;
j ++;
}
else{
i = i - j + 1;//主串指针的回溯,到上一次匹配起点的下一位
j = 0; //子串又复位
}
if(j == len1)return i-1;
}
return -1;
}
最坏情况下,时间复杂度为len1 * len2 ,对于串的长度很敏感,所以需要改进。
启发
请看示例:
s1=“abebbbadacah”
s2=“abc”
前两个字符’a’‘b’匹配成功,而且互不相同
如果下次回溯指针指向s1[1],也肯定不能匹配,因为’a’!=‘b’
那么,回溯指针应该指向和s2[0]第一次相同的index对应的主串中的位置
s1=“ababcbadacah”
s2=“ababa”
第一次相同的index = 2 对应 s1[2] 这样就做到了一定优化
此时,又发现s2[3]也是和s2[3]也是相同的,如果我知道这个信息,那么s1[3]也是默认匹配的,我就可以直接从s1[4](当前位置)去和s2[2]匹配
由此,我们可以想到,如果到当前位置不匹配了,
(1)子串前面的字符又互不相同,就不用回溯了;
(2)如果有出现上述所说的后缀与前缀相同,即后缀可以代替前缀的情况,回溯指针本来应该指向第一个相同的index处,但是都知道可以替代了,就知道默认匹配了,直接从当前位置和后缀代替前缀部分的下一位匹配;
(3)如果只是中间部分匹配,不算是后缀与前缀匹配,也会带来默认不匹配的信息,也不用回溯
设想
如果我能存储子串中当前位前面的子串这种后缀可以代替前缀的数目,一旦不匹配,我就知道可以把子串的指针修改到这个数目对应位的下一位,再开始。这样的话,如果控制不在主串的当前位置死循环呢,就要注意第0位的信息应该是特别的,提示我们移动主串的指针。
KMP算法
next数组的实现
这个数组也就是我设想的存储后缀可以代替前缀数目的存储结构。
怎么实现呢?
如果用一般的思路解决这个前缀与后缀匹配的最大数目的问题,时间复杂度会达到O(n^2),没有达到优化的目的。
那么,就来学习学习别人的思路吧。
上代码:
void Getnext(int next[],String t)
{
int j=0,k=-1;//k指向前缀末尾,j指向后缀末尾
next[0]=-1;//使第0位特别 巧妙的设置
//next[1]=0;
/*如果j,k的初始值分别为1,0,是需要这个定义的,但是j=0;k=-1的初始状
态刚好可以和指针回到next[0],即主串(后缀)当前位置完全不合适情况一致,
可以省略啦*/
while(j<t.length-1)
{
if(k == -1 || t[j] == t[k])//局部思想
{
j++;k++;
next[j] = k;//先++再确立next信息的原因:后缀不包含当前位,新指针是数目对应位的下一位
}
else k = next[k];/*如果不匹配,为了优化时间,其实又回到了刚才主串
和子串匹配的思想,后缀相当于主串,指针不回溯,前缀相当于子串,回到
后缀可以代替前缀的数目的下一位,正好k以前的信息已经算出来了*/
}
}
KMP匹配
int MatchKmp(char *s1, int len1, char *s2, int len2){
if(len1 < len2)return -1;
int i = 0, j = 0;
while(i < len1){
if(j == -1 || s1[i] == s2[j]){//缘分 完全不匹配和匹配的操作相同
i ++;
j ++;
}
else j = next[j];
if(j == len2)return i-1;
}
return -1;
}
再改进
有一个小小的问题,就是当当前子串的位置s2[j]已经不匹配了,根据我们的思路,s1[i]该和s2[next[j]]匹配了,那如果s2[j] = s2[next[j]]呢,那这一次匹配也是没有意义的,为了优化,那就把这种情况也设置成默认不匹配吧,于是改进next数组
void Getnext(int next[],String t)
{
int j=0,k=-1;
next[0]=-1;
while(j<t.length-1)
{
if(k == -1 || t[j] == t[k])
{
j++;k++;
if(t[j]==t[k])//当两个字符相同时,就跳过
next[j] = next[k];
else
next[j] = k;
}
else k = next[k];
}
}
撒花
整个的梳理就结束啦,感觉一下明朗了很多,开心~
虽然以后可能还会不明白,再回顾这篇博客时,向此刻的自己致以respect
*借鉴博客:
KMP算法
https://blog.csdn.net/dark_cy/article/details/88698736
感谢~