【学完这个之后,我感觉,,好多算法名都是人名啊】
参考两篇神级讲解,安利一波:
https://www.cnblogs.com/yjiyjige/p/3263858.html
https://blog.csdn.net/starstar1992/article/details/54913261/
解决问题:
KMP算法是用来处理字符串匹配问题的,也就是说,当给你两个字符串,你需要回答B串是否是A串的子串或者是A串是否包含B串。
我们称等待匹配的A串为主串(母串),用来匹配的B串的为模式串。通常解决这个问题是用来枚举A串的位置与B匹配,然后验证是否匹配;假如说A串的长度为n,B串的长度为m,算法的复杂度则为O(nm),多数情况下枚举的复杂度是可以接受的,但也会有最坏的情况,下面介绍一种在最坏的情况下,时间复杂度人仍为O(n)的算法之一,KMP算法。
主要思路:
充分利用模式串的自身的性质,在与主串匹配字符出现匹配失败的情况时,移动最大的距离,节省时间。(并非失败后一个一个字符的向后挪)【看不明白就瞅这句话,我基本上就是被这句话点透的,这句话就是整个算法的方向目的】
【这东西我看一下午一上午刚明白,尽量说清楚吧】
1》原始匹配方法:(上方母,下方子串)
从子串的第一个字符开始匹配,前三个匹配顺利,到第四个:
出现一颗老鼠屎,我们会这样移动子串:
然后再循环从子串的第一个开始匹配,直至到主串末尾。
2》上述方法当然不错,朴素,符合我蒟蒻的气质,但是大神们是忍受不了这样的遍历的:如果你遇见了四位置的老鼠屎,你会只向后移动一个位置吗?
最起码我会将子串移动到下一个“A”上,再进行遍历,大神们依然不能忍受,你就只会挪与子串的第一个字符相同的位置,不会挪到与前两个都相同的吗?不会挪到前三个相同的吗?不会挪到与前四个。。(直至我弱弱的说,人家子串就四个字符。。);
这个被嫌弃的过程就是算法的过程,当出现字符串不匹配时,大神们是这样做的:
即:字符A与字符B不同,整个子串就需要后移,移动到哪里呢,肯定整个子串不会移动到字符A的后面去(最多移动到字符A的位置,想想为嘛),即
在已经遍历过的位置上寻找和 子串的前 k 个字符相同的位置(如图,字串中 前缀区域 3 和 母串中已经遍历过的 区域 2 是相同的,就将 子串挪 到 区域3 与 区域2 刚好匹配的位置,再进行判断);
看出蹊跷了吗?在已经遍历过的区域找与子串某前缀相同区域,既然已经遍历过并且没有发现老鼠屎,那么主串中该区域一定和字串中的老鼠屎“B”之前的某一区域也匹配(即区域4);【有点绕,好好悟一下】;
现在可以得到结论,在子串的老鼠屎之前,有一段前缀和后缀相同,则将前缀直接移动到后缀位置;若没有这样的前后缀,则整串移动到位置A处,开始新的旅程。【注意是子串的“老鼠屎”之前,老鼠屎在子串爆发的位置是不可预测的】
3》问题是,谁都不知道在匹配的过程中,谁变成了老鼠屎,子串从位置0到 n 都有可能不匹配,怎么知道究竟移动到哪里?
这就是另一个重要问题,要先对子串的含有前k个字符的子串的前缀后缀相同的 “缀”的大小 进行打表,方便在子串的任意位置不匹配时,可以直接移动前缀到后缀的位置;
例如:
考察目标字符串ptr:
ababaca
这里我们要计算一个长度为m的转移函数next。
next数组的含义就是一个固定字符串的最长前缀和最长后缀相同的长度。
比如:abcjkdabc,那么这个数组的最长前缀和最长后缀相同必然是abc。
cbcbc,最长前缀和最长后缀相同是cbc。
abcbc,最长前缀和最长后缀相同是不存在的。
**注意最长前缀:是说以第一个字符开始,但是不包含最后一个字符。
比如aaaa相同的最长前缀和最长后缀是aaa。**
对于目标字符串ptr,ababaca,长度是7,所以next[0],next[1],next[2],next[3],next[4],next[5],next[6]分别计算的是
a,ab,aba,abab,ababa,ababac,ababaca的相同的最长前缀和最长后缀的长度。由于a,ab,aba,abab,ababa,ababac,ababaca的相同的最长前缀和最长后缀是“”,“”,“a”,“ab”,“aba”,“”,“a”,所以next数组的值是[-1,-1,0,1,2,-1,0],这里-1表示不存在,0表示存在长度为1,2表示存在长度为3。这是为了和代码相对应。
代码实现:
KMP的代码实现包括两部分,一个是子串与主串的判定移动,另一个子串的打表,代码实现我现在很弱,先粘上大佬代码:
预处理子串:
p[1]=0;
j=0;
for(int i=1;i<m;++i){
while(j>0&&b[j+1]!=b[i+1])j=p[j];
//不能继续匹配且j还没减到0,考虑退一步;
if(b[j+1]==b[i+1])++j;//能匹配,j的值加1;
p[i+1]=j;
}
两串匹配:
j=0;
for(int i=1;i<n;++i){
while(j>0&&b[j+1]!=a[i+1])j=p[j];
//不能继续匹配且j还没减到0,减小j的值
if(b[j+1]==a[i+1])++j;//能继续匹配j,j的值加1;
if(j==m){//找到一处能匹配
printf("%d\n",i+1-m+1);//子串串首在母串的位置
j=p[i];//继续寻找匹配值
}
}
可以说是比较坑了,我说我是从BFS过来的你信吗,,最短路径输出->哈希表->字符串hash->KMP,,我要重新投入最短路的怀抱!