KMP算法的全称为:Knuth-Morris-Pratt 。朴素算法效率不高根本原因在于进行了多次重复的比较,没有充分利用已匹配的字符的信息。设文本为T,匹配模式为P;例如:
T : a b a b c d a b b a b a b a d
P : a b a b a b a
上面模式P与文本匹配,在从开始匹配到字符T[4]!=P[4]时,普通做法是将字符串后移一位,继续从开头匹配。在KMP算法中,我们有已知信息:P[0...3]==T[0...3],也就是说这一部分是匹配好的,如果下次匹配仅仅移动一位,那么刚才匹配好的子串之间对应关系如下:
T[0..3] : a b a b
P[0...3]: a b a b
显然划线部分就是不匹配的,更何况整个串匹配,所以这是一个无用的移位。如果这次移动的是2位,则如下:
T[0...3] : a b a b
P[0...3]: a b a b
显然这次子串部分匹配上了,这时候再去检查剩下部分是否匹配就可以了。至于如何得知每次该移动的位置,我们需要填一个Next[]表。
首先弄清楚字符串的前缀和后缀。设x,y,z是三个非空串xy表示两个串的连接,若x=yz ;则y是x的前缀,z是x的后缀。比如abab的前缀有:a,ab,aba。后缀有:b,ab,bab .我们定义的Next[j]就是表示在P[0...j-1]中前缀与后缀匹配的最大长度。j可以认为是P[0...1-j]的长度。对于P的Next[j]表格如下:
j 0 1 2 3 4 5 6
Next[j] -1 0 0 1 2 3 4
有了Next[j]表格,下次在P[j]处不匹配时,只需要移动j-Next[j]的距离。Next[0]=-1表示模式P应该移动到不匹配文字后面去。计算Next[j]就是字符串P与本身的匹配.设Next[0]=-1假设Next[i-1]已经确定,现在要来确定Next[i] 。分两种情况:
1)设 j= Next[i-1] ,若P[i-1]==P[j] ,这说明当前的后缀比前次的后缀长度多一个字符,于是Next[i]=j+1.
2)若 P[i-1]!=P[j], 这时应该去 P[ 0...j-1中去找匹配的串,j=Next[j] ,比较P[i-1]与P[j ].如此循环,当遇到相等或者遇到j==-1为止,然后Next[i]=j+1;
在KMP的算法中,在T的每个字符至多比较两次。最后的时间复杂度为O(m+n),预处理时间为O(m).主要是弄清楚Next[j的含义。
<span style="font-size:14px;">#include<iostream>
#include<string>
using namespace std;
#define SIZE 10003
int Next[SIZE];
int ans;
int postion[1000];
int main(){
int i, j, m, n;
Next[0] = -1;
string P, T;
cin >> P >> T;
m = P.size();
n = T.size();
for (i = 1; i <=m; i++){ //计算Next[j]
j = Next[i - 1];
while (j != -1 && P[i - 1] != P[j])
j = Next[j];
Next[i] = j + 1;
}
//--------------------------------------------------------
ans = i=j=0;
while (i <n){
while (j == -1 || (j < m&&P[j] == T[i])){ //j=-1表示第一个字符都没有匹配上
j++;
i++;
}
if (j == m)
postion[ans++] = i - m;
j = Next[j]; //更新j的位置
}
//-------------------------------------------------------------------
for (i = 0; i < ans; i++)
cout<<postion[i] << endl;
return 0;
}</span>