*介绍:
KMP算法是在给定的一串字符串中查找是否有目标串。
*分析:
1、一般解法
对于以上问题,假如用朴素算法,我们容易想到:用目标串(长度为n)的第1个字符依次与给定串(长度为m)的第1个至最后一个字符对齐匹配,直到找到目标串为止。这样算法复杂度是m*n。
2、KMP算法:
思路分析:
KMP算法通过建立一个数组(一般命名为next)来记录目标串的自身特征从而简化匹配过程,令算法的复杂度降至m+n。
举例,假设目标串为: a b c a b c d d e a
对应next数组的值为: 0 0 0 1 2 3 0 0 0 1
通过观察我们会发现next数组记录的是以当前字符为后缀,以第一个字符为前缀的相同字符串(这样的字符串可能有很多个)其中最长的一个的长度。例如:对于例子中的第一个b,以他为后缀,我们可以找到2个字符串(ab,b) 以第一个字符a为前缀的字符串也有3个(a,ab),由于从第1个字符到当前字符不再计算范围之内所以最长长度为0;我们再看第2个b,以他为后缀的字符串有(abcab,bcab,cab,ab,b),以第1个字符a为前缀的字符串有(a,ab,abc,abca,abcab)其中相同且最长的是ab这个字符串所以next数组的值为2。
知道next数组是怎样得出的之后我们进一步发现可以这样理解:next数组记录的是与当前字符地位相当的字符的位置。例如,字符串中的第2个b对应的next数组值为2,我们由此考察字符串中第2个字符恰好也是b,而且从第1个字符开始到我们找到的b为止的字符串,与刚开始的b前面相应长度的字符串完全一样(都是ab),同样我们可以考察其他字符,next数组中的0则代表找不到一个相当的字符。
分析到这里我们自然想到:在字符串的匹配过程中我们假如刚开始匹配的很好,而下一个字符就不匹配了,我们就可以通过next数组找到一个字符串来代替当前串继续匹配,而不用像朴素算法那样回到开始。这正是KMP算法省时间的原因。
关于next数组的获取:
我们得到next数组取值的过程呢其实是在令next数组内部与自身匹配,所以思路和以上极为相似。next数组需要借助前面的取值来确定后一个数的取值。假如当前字符(第2个b)和其前面的字符(第2个a)的next数组指向的字符(第1个a)的后一个字符(第1个b)相同,那么当前字符的next数组值就是前一个字符next数组值加1;假如不相同(例如第1个d),我们就需要以同样的思路考察与当前字符前面的字符(第2个c)地位相当的字符(即next数组指向的字符——第1个c),我们发现依然不相同并且此时已经没有与第1个c地位相当的字符,所以next数组的值只能是0。整个过程我们仍是在利用地位相当的字符,所以next数组获取的代码与KMP算法的主代码非常像。
*代码:
#include<iostream> #include<cstring> using namespace std; int next[1000],lent,lenp; char text[1000],part[1000]; void get_next()//获取next数组 { next[0]=-1; int i=0,j=-1; while(i<lenp) { if(part[i]==part[j]||j==-1) { i++; j++; next[i]=j; } else j=next[j]; } } int KMP()//主代码 { int i=0,j=0; get_next(); while(i<lent&&j<lenp) { if(j==-1||text[i]==part[j]) { i++; j++; } else j=next[j]; } if(j>=lenp)return i-lenp; else return -1; } int main() { cin>>text>>part; lenp=strlen(part); lent=strlen(text); cout<<KMP();//此处是输出匹配开始的数组下标 return 0; }