KMP作为字符串搜索的关键算法,打破了BF时间复杂度O(n*m)的低效思维,利用next数组,即前缀表来减少匹配次数,从而减少运行时间,其复杂度为O(n+m)。(n是主串的长度,因为最坏情况下主串的每一个字符都被比较过,m是模式串的长度)。
我这里主要讲的是前缀表的计算方法和这种思维想法,仅个人见解。
关于前缀表的概念:
前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。
首先了解一下什么是前缀,后缀
-
前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串。
-
后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。
对于next数组,不同人,不同资料有不同的给法,在我目前学习的数据结构书中,给出的字符串是以下标1开始存储的,而我在网上资料以及大多接触的都是从0开始存储,因为这种存储方便输入。但二者并没有太大区别。
先讲一下从下标1开始存储的字符串
{ 0 ,j=1
next[
j]= { Max { k |1<k<j && t1t2t..t
k-1 = t(j-k+1) + t..+t
j-1 }
{1 k=1
***第一次用csdn发布笔记,不会搞公式……***
我用文字对他的解释为:
next数组可以理解为一种前缀表,记录的是该字符之前(不包括该字符)的串满足最大相同前后缀的长度;
void bookNext(int *next,const string& s){
int i=1;next[1]=0;int j=0;
while(i<s.length()){
if(j==0 || s[i]==s[j]){++i;++j;next[i]=j;}
else j=next[j];
}
}
因此按照书上的解释,可以给出这几行代码
在next数组的存值时,可以理解为模式串与模式串的匹配,来查找最长相等前后缀,并将这个值存在next数组中,在匹配过程中出现失配也可以通过next数组回溯。
***手动分割线***
下面是以下标为0存储的next数组获取
这里的next数组与上述有些不同:
以该字符结尾的串满足最大相同前后缀的长度;
void getNext(int* next, const string& s) {
int j = 0;
next[0] = 0;
for(int i = 1; i < s.size(); i++) {
while (j > 0 && s[i] != s[j]) { // j要保证大于0,因为下面有取j-1作为数组下标的操作
j = next[j - 1]; // 注意这里,是要找前一位的对应的回退位置了
}
if (s[i] == s[j]) {
j++;
}
next[i] = j;
}
}
具体next数组实现。
下面给出完整的KMP代码
class Solution {public:
void getNext(int* next, const string& s) {
int j = 0;
next[0] = 0; next[0]初值为0
for(int i = 1; i < s.size(); 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) { //haystack为主串 needle为模板串
if (needle.size() == 0) {
return 0;
}
int next[needle.size()];
getNext(next, needle);
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;
}
};