好吧,我承认,这个算法真的很不好理解(应该是水平有限吧)。一开始,从严蔚敏的那本书上看了KMP算法,一开始没看懂,后来看了几遍还是没看懂,发现这个next数组挺神奇的,算法的原理其实不难,难的就是那个巧妙的next数组,花了很长时间思考这个数组。
1)算法介绍
KMP算法解决的问题是字符匹配,是由Knuth–Morris–Pratt共同开发出来的,这个算法把字符匹配的时间复杂度缩小到O(m+n),而空间复杂度也只有O(m),n是target的长度,m是pattern的长度,在此算法在发明之前并不是没有如此高效的算法,但是原算法比较复杂。
2)神奇的next数组
我们设主串S=abcabcabca,模式串T=abcabx
KMP第一趟匹配:
i=6
S : a b c a b c a b c a
位置 : 1 2 3 4 5 6 7 8 9 10
T : a b c a b x
位置 : 1 2 3 4 5 6
j=6
第一次匹配到第6个位置的时候失败了,按照朴素的算法,i要回溯到第2个位置,j要回溯到第1个位置重新匹配。KMP的话,主串中的i是不会回溯,模式串中的j回溯也不会回溯到第1个位置。注意这里是关键,i不用回溯就可以完成整个字符串的匹配。为什么i不需要回溯呢?
我们把匹配成功的前5个字符看一下。
1位置的前缀子串为:a , ab , abc , abca
5位置的后缀子串为:bcab , cab , ab , b
我们观察发现两组里面都有一个ab,你能看出点什么东西么,好的,先不管这个
我们就按照朴素的算法来看,i回溯到第2第3位置都会在前5个字符中匹配失败
朴素匹配:
i=4
S : a b c a b c a b c a
位置 : 1 2 3 4 5 6 7 8 9 10
T : a b c a b x
位置 : 1 2 3 4 5 6
j=1
当回溯到第4个位置的时候,成功匹配的字符为ab,然后再去判断S串的第6个字符和P串的第3个位置。这个然后我们先不管,观察S中和P匹配的ab,在第一趟匹配的时候S中的ab是和P中前5个字符的最后两个匹配的,而这一次匹配则是和P中前两个字符匹配的。能发现点什么东西么?
不需要让i回溯到之前的位置重新匹配,只需要找到在P串前5个字符中第一个位置的前缀子串和最后一个位置的后缀子串相等并且串长最大的那一对子串,让j指向前缀子串最后一个字符的下一个位置3,和i所指向的6进行比较。往后遇见不匹配的时候采取和这个一样的方法。
KMP第二趟匹配:
i=6
S : a b c a b c a b c a
位置: 1 2 3 4 5 6 7 8 9 10
T : a b c a b x
位置: 1 2 3 4 5 6
j=3
KMP算法解决的问题是字符匹配,是由Knuth–Morris–Pratt共同开发出来的,这个算法把字符匹配的时间复杂度缩小到O(m+n),而空间复杂度也只有O(m),n是target的长度,m是pattern的长度,在此算法在发明之前并不是没有如此高效的算法,但是原算法比较复杂。
2)神奇的next数组
我们设主串S=abcabcabca,模式串T=abcabx
KMP第一趟匹配:
i=6
S : a b c a b c a b c a
位置 : 1 2 3 4 5 6 7 8 9 10
T : a b c a b x
位置 : 1 2 3 4 5 6
j=6
第一次匹配到第6个位置的时候失败了,按照朴素的算法,i要回溯到第2个位置,j要回溯到第1个位置重新匹配。KMP的话,主串中的i是不会回溯,模式串中的j回溯也不会回溯到第1个位置。注意这里是关键,i不用回溯就可以完成整个字符串的匹配。为什么i不需要回溯呢?
我们把匹配成功的前5个字符看一下。
1位置的前缀子串为:a , ab , abc , abca
5位置的后缀子串为:bcab , cab , ab , b
我们观察发现两组里面都有一个ab,你能看出点什么东西么,好的,先不管这个
我们就按照朴素的算法来看,i回溯到第2第3位置都会在前5个字符中匹配失败
朴素匹配:
i=4
S : a b c a b c a b c a
位置 : 1 2 3 4 5 6 7 8 9 10
T : a b c a b x
位置 : 1 2 3 4 5 6
j=1
当回溯到第4个位置的时候,成功匹配的字符为ab,然后再去判断S串的第6个字符和P串的第3个位置。这个然后我们先不管,观察S中和P匹配的ab,在第一趟匹配的时候S中的ab是和P中前5个字符的最后两个匹配的,而这一次匹配则是和P中前两个字符匹配的。能发现点什么东西么?
不需要让i回溯到之前的位置重新匹配,只需要找到在P串前5个字符中第一个位置的前缀子串和最后一个位置的后缀子串相等并且串长最大的那一对子串,让j指向前缀子串最后一个字符的下一个位置3,和i所指向的6进行比较。往后遇见不匹配的时候采取和这个一样的方法。
KMP第二趟匹配:
i=6
S : a b c a b c a b c a
位置: 1 2 3 4 5 6 7 8 9 10
T : a b c a b x
位置: 1 2 3 4 5 6
j=3
这个时候就需要next数组的建立了,next[6]存储的就是前5个字符组成的字符串中的第一个位置的前缀子串和最后一个位置的后缀子串相等并且串长最大的那一对子串的最后一个字符的下一个位置,也就是3,也就是和P串中第3个位置匹配。
3)KMP模式匹配过程
天朝有句古语“no picture you say a j8.”,通过这个图片可以更好的理解这个算法的过程。
3)KMP模式匹配过程
天朝有句古语“no picture you say a j8.”,通过这个图片可以更好的理解这个算法的过程。
目标字串annbcdanacadsannannabnna 匹配模式annacanna
图中黄色标注的行为通过next数组跳过的行,绿色为匹配序列,红色为失配位置
4)上代码
好了,相信大家已经很明白了,上代码,先来个简单的传统方法
好了,相信大家已经很明白了,上代码,先来个简单的传统方法
int bf_match(char *s,char *t)
{
int i=0,j=0;
while(i<strlen(s) && j<strlen(t)){
if(s[i]==t[j]){//当前匹配成功后跳到下一个字符比较
i++;
j++;
}else{//匹配失败后i回溯到匹配前的下一个位置,j复位
i=i-j+1;
j=0;
}
}
if(j>=strlen(t))
return i-strlen(t)+1;
else
return -1;
}
void get_next(char *t,int *next)
{
int i=0,j=-1;
next[0]=-1;
while(i<strlen(t)){
if(j==-1 || t[i]==t[j]){
i++;
j++;
next[i]=j;//优化前
/*优化后
if(t[i]==t[j])
next[i]=j;
else
next[i]=next[j];
*/
}else{
j=next[j];
}
}
}
int kmp_match(char *s,char *t)
{
int i=0,j=0;
int next[64];
get_next(t,next);
while(i<strlen(s) && j<strlen(t)){
if(j==-1 || s[i]==t[j]){
i++;
j++;
}else{
j=next[j];
}
}
if(j>=strlen(t))
return i-strlen(t)+1;
else
return -1;
}