KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为Knuth——Morris——Pratt操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串P(长度为m)与主串T(长度为n)的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度为O(m+n)。
以下为我理解内容:
相对暴力的解决方法,KMP在T的[ i-j, i-1 ]与P的[0, j -1]匹配且T[i]!=P[j]时,索引j下一步指向的位置标记为next(j).
即在KMP算法中,要求next函数满足:T的[ i-next(j), i-1 ] 与 P的[0, next(j) - 1] 是匹配的 。
又因为T的[ i-j, i-1 ]与P的[0, j -1]匹配,所以next函数应该满足P的[0, next(j) - 1] 与 P的[j - next(j), j -1]是匹配的。即:
next(j)的含义应该是在P字符串的[ 0, j - 1]中满足前缀字符串和后缀字符串匹配的最大字符串长度。
public static int[] Next(String p) {
int[] next = new int[p.length];
next[0] = -1;
int j = 0;
int k = -1;
while (j < p.length() - 1) {
//由于字符串本身是其自身的前缀子字符串和后缀子字符串,所以对p只计算[0,length - 2]最长字符串中的next函数值
if (k == -1 || p[j] == p[k])
//j为p的[0,j - 1]子字符串的下一个字符
//k为p的[0,j - 1]子字符串
next[++j] = ++k;
else
k = next[k];
}
return next;
}
next函数的实现:
由于next(j)为P字符串的[ 0, j - 1]子字符串中满足前缀字符串和后缀字符串匹配的最大字符串长度。所以
- 当j==0时,子字符串[ 0, j - 1]不存在,设定next(0) = -1。
- 当j==1时,子字符串[ 0, j - 1]是[0, 0],由于字符串本身是其自己的前缀和后缀,所以设定next(1) = 0;
- 当j > 1且p[j] == p[k]时,由于子字符串[ 0, k - 1] ( = [ j - k, j - 1] )为子字符串[ 0, j - 1]的最长前后缀匹配字符串,即next(j) = k.所以next( j + 1 ) = k + 1,即子字符串[ 0, j ]的最长前后缀匹配字符串长度为k+1;然后继续去比较p[j + 1] 和 p[k+ 1]是否相等。
- 当j > 1时且p[j] != p[k]时,此时需要寻找子字符串[ 0, j ]的最长前后缀匹配字符串。显然,next(j + 1)肯定小于next(j) = k。所以可以转变思路为寻找匹配的字符串[ 0, k - 1 - x]和[ j - k + x + 1, j]。
又由于next(j) = k
所以[ j - k , j - 1] = [0, k - 1 ],即[ j - k + x + 1 , j - 1] = [ x + 1, k - 1 ]
即问题可转换思路为寻找字符串p[0, k]+p[j]的最长前后缀匹配字符串。由如下解释可知,此时next( j + 1 )可通过跳转至步骤3顺序执行进行求解:
当p[j] == p[k]时,字符串[ 0, j ]的最长前后缀匹配字符串长度next( j + 1 ) = next (k ) + 1
当p[j] != p[k]时,字符串[ 0, j ]的最长前后缀匹配字符串长度next( j + 1 )求解跳转至第4步继续求解。
vector<int> KMPnext(string p){
int size = p.size();
vector<int> next(size, 0);
next[0] = -1;
int j = 0, k = -1;
while(j < size -1){
if(k == -1 || p[j] == p[k])
next[++j] = ++k;
else
k = next[k];
}
return next;
}
int strStr(string haystack, string needle) {
if(needle.empty())
return 0;
int m = haystack.size(), n = needle.size();
if(m < n)
return -1;
vector<int> next = KMPnext(needle);
for(int i = 0, j= 0; i < m ;){
if(haystack[i] == needle[j]){
i++;
j++;
}
if(j == n )
return i- j;
if ( i < m &&haystack[i] != needle[j]) {
j?j= next[j] : i++;
}
}
return -1;
}