KMP算法作用
KMP主要应用在字符串匹配上。
KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。
KMP算法的实现
前缀表
前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。KMP算法中使用的next数组就是一个前缀表。
前缀表记录的是字符串到达下标i之前(包括i)的字符串具有的最大公共前后缀。
例如aabbaac的前缀表为[0,1,0,2,3,4,0]
什么是最大公共前后缀,首先了解下前后缀概念,前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串,比如aabbaac中的前缀有:a,aa,aab,aabb,aabba,aabbaa;后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串,如aabbaac的后缀有c,ac,aac,baac,bbaac,abbaac。最大公共前后缀是两个前缀和后缀共有的最大长度的字符串,比如aa的最大公共前后缀为1,aabbaa为4,aabb为2。
为什么使用前缀表?
当我们要匹配字符串时,到某个字符匹配失败时,前缀表会告诉我们要匹配的字符串返回到哪里最合适。
例如,在字符串aabaabaac中查找字符串aabaac,aabaac的前缀表为{0,1,0,1,2,0}当查找到第6个字符时b!=c,则返回到前缀表中c前一个字符对应的最大公共前后缀2,及字符b。再从该位置与所匹配的字符串的当前位置进行匹配。
next数组
前缀表可以作为next数组,但一般将前缀表向右移动一位,或每个元素减1作为next数组,这样的原因可能是比较起来比较方便,每次返回的是当前位置next下标。
构造next数组(重点)
构造next数组有需要注意的有三点
1.初始化
根据你使用的next数组形式,进行对应的初始化,例如直接将前缀表作为next数组,而next[0]=0,如果以减一的形式组成,则next[0]=-1;如果向右移动一位,则next[0]=-1,next[1]=0.
定义前后缀末位的初始位置,前缀末位初始位置为1,后缀为0。
2.处理下一位前后缀末位相同的情况
当前后缀末位相同时,最大公共前后缀加1.这很好理解,如图将前缀表作为next数组,next[8]=5,此时下一位末位相同,相当于加上下一位的前后缀也相同,及最大公共前后缀加1,next[9]=6,及next[j+1]=next[j]+1.
3.处理下一位前后缀末位不同的情况
当前后缀末位不同时,返回到next[j-1]位置字符再次进行比较,循环,直至找到匹配字符然后j+1,或者返回到j=0.
该方法和利用next数组查询方法相同,如图所示,当字符b和c不匹配时,需要返回到一个合适位置,该位置可以由以上一位结尾的连续子串的后缀的最大公共前后缀长度获得。及s[j-1]=s[i-1],s[j-k]=s[i-k],s表示字符串,i表示后缀末位,j表示前缀末位,k表示最大公共前后缀长度。
我们需要获得的前缀表的数据是从首位开始的连续子串的长度,因此需要将使用的后缀替换位对应的前缀,及此刻以从0开始以j-1结尾的字串,其对应的最大公共前后缀长度位next[j-1],此时比较的下标返回到j-1,及s[2],b和c不匹配,通过前后缀对应关系,返回s[2]之前的连续字串的最大公共前后缀长度,及next[1],a!=c,再次返回j=next[0],此时到达边界,将0赋给next[i]。
可以进行循环遍历返回的原因是每次使用的前后缀都是之前前后缀的最大公共前后缀。比如返回到j=2后,之前的连续子串aa,在c的前面也能找到。
代码(以力扣28题举例,next数组等于前缀表)
class Solution {
public:
void getnext(const string&s,int *next)
{
int j=0;//最长公共前后缀长度,前缀末尾
next[0]=j;
for(int i=1;i<s.size();i++)//i表示后缀末尾
{
while(j>0&&s[i]!=s[j])
{
j=next[j-1];
}
if(s[i]==s[j])
{
j++;
}
next[i]=j;
}
}
int strStr(string haystack, string needle) {
if(needle.size()==0)
return 0;
vector<int>next(needle.size());
getnext(needle,&next[0]);
int j=0;
for(int i=0;i<haystack.size();i++)
{
while(j>0 &&haystack[i]!=needle[j])
{
j=next[j-1];
}
if(haystack[i]==needle[j])
{
j++;
}
if(j==needle.size())
return (i-needle.size()+1);
}
return -1;
}
};