KMP的思想就是当字符串发生不匹配时,根据已经知道的一部分匹配内容来避免重头匹配
前缀是什么?
前缀是从首字母开始到任意位置i结束的特殊子串。真前缀不包含尾字母
例:aabdfdf的真前缀
a
aa
aab
aabd
aabdf
aabdfd
什么是后缀?
后缀是从某一位置i开始到字符串结尾的特殊子串。真后缀不包含首字母
例:aabdfdf的真后缀
f
df
fdf
dfdf
bdfdf
abdfdf
什么是前缀表?
前缀表记录了当前位置i(包括i)之前,字符串中相等的真前后缀长度
前缀表用来记录当前位置,文本和模式串不匹配时,模式串应该从哪里重新匹配
a | a | b | a | a | f | 模式串 |
0 | 1 | 0 | 1 | 2 | 0 | 前缀表 |
让我们来看看前缀表怎么得到的
前缀表记录了当前位置i(包括i)之前,字符串中相等的(真)前后缀最大长度
i=0时
长度为1的a,真前后缀长度为0,相等的真前后缀长度为0
i=1时
长度为2的aa,真前缀为a,真后缀为a,相等的真前后缀长度为1
i=2时
长度为3的aab,真前缀a,aa,真后缀为b,ab,无相等的真前后缀,相等的真前后缀长度为0
i=3时
长度为4的aaba,真前缀a,aa,aab.真后缀为a,ba,aba,相等的真前后缀为a,相等的真前后缀长度为1
i=4时
长度为5的aabaa真前缀为a,aa,aab,aaba真后缀为a,aa,baa,abaa,相等的真前后缀为aa,a相等的真前后缀最大长度为2
i=5时
长度为6的aabaaf,真前缀为a,aa,aab,aaba,aabaa真后缀为f,af,aaf,baaf,abaaf无相等的真前后缀,相等的真前后缀长度为0
前缀表有什么用?
很多教材都告诉我们要建立前缀表,但是前缀表到底起什么作用我们确不得而知,导致看代码总迷迷糊糊的.
前缀表能告诉我们原串 haystack
[i]与模式串needle[j]不匹配时,j应该移动到哪个位置后,继续比较 haystack
[i]与needle[j],所以前缀表具有告诉我们当前位置匹配失败,跳到之前已经匹配过的地方的能力
如何写代码得到前缀表?
定义两个指针i和j
j指向真前缀末尾位置(同时j+1是最长相等真前后缀的长度)
i指向真后缀末尾位置。
初始化:j从0开始遍历模式串,i从1开始(因为真后缀不包括首字符)
这里是动态规划的思想
真前缀末尾位置的字符==i指向真后缀末尾位置的字符,最长相等前一最长真前后缀的长度加1
真前缀末尾位置的字符!=i指向真后缀末尾位置的字符,根据KMP的思想,我们可以回退到可能匹配的位置,j = next[j-1]如果匹配成功 ,长度等于j+1,匹配不成功继续回退
边界条件,当回退到j=0时并且s[i]和s[j]不相等,next[i]=j.(j=0,此时最长相等真前后缀长度为0)
代码如下:
你可以理解为这是在边使用next数组边建立next数组的过程
图解:
void getNext(int* next, const string& s){
int j = 0;//初始化,next数组起始位置为0
next[0] = j;
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++;//如果匹配,最长相等真前后缀长度加1
}
next[i] = j;//j ==0 && s[i]==s[j] 的情况在这里体现
}
}
如何使用前缀表?
模式串
模式串 | a | a | b | a | a | f |
下标 | 0 | 1 | 2 | 3 | 4 | 5 |
i
文本 | a | a | b | a | a | b | a | a | f | a |
模式串 | a | a | b | a | a | f | ||||
next | 0 | 1 | 0 | 1 | 2 | 0 |
j
b和f 不匹配了,f的前一个坐标对应的next[j-1]是j需要回退到的位置,j要退回到模式串索引为2的位置
i
文本 | a | a | b | a | a | b | a | a | f | a |
模式串 | a | a | b | a | a | f | ||||
next | 0 | 1 | 0 | 1 | 2 | 0 |
j
然后从i==5和j==2的位置开始比较
假设n为文本串长度,m为模式串长度,因为在匹配的过程中,根据前缀表不断调整匹配的位置,可以看出匹配的过程是O(n),之前还要单独生成next数组,时间复杂度是O(m)。所以整个KMP算法的时间复杂度是O(n+m)的。
暴力的解法显而易见是O(n × m),所以KMP在字符串匹配中极大地提高了搜索的效率。
KMP算法如何实现匹配过程?
int kmp(string l,string needle){
if(needle.size( ) == 0){
reutrn 0;//模式串长度为0
}
int next[needle.size( )];//建立next数组
getNext(next, needle);
int j = 0;
for(int i = 0; i < l.size( ); i++){//开始匹配
while (j > 0 && l[i] != needle[j]){//匹配不成功回退
j = next[j-1];
}
if( l[i] == needle[j]){
j++;//匹配成功就匹配下一个字符
}
if (j == needle.size){//匹配完了就返回首字母的位置
return (i-needle.size( )+1);
}
return -1;
}
注:有些程序员喜欢讲next数组集体减1后使用,这仅仅是KMP算法实现上的问题,思想都是一样的