一种改进的字符串匹配算法,由D.E.Knuth与V.R.Pratt和J.H.Morris同时发现,因此称之为KMP算法。此算法可以在O(n+m)的时间数量级上完成串的模式匹配操作,其基本思想是
:每当匹配过程中出现字符串比较不等时,不需回溯指针,而是利用已经得到的“部分匹配”结果将模式向右“滑动”尽可能远的一段距离,继续进行比较。
假如,A="abababaababacb",B="ababacb",我们来看看KMP是怎么工作的。我们用两个指针 i 和 j 分别表示,A[i-j+1…i] 与 B[1...j] 完全相等。也就是说,i 是不断增加的,随着 i 的增加 j 相应地变化,且 j 满足以 A[i] 结尾的长度为 j 的字符串正好匹配B串的前 j 个字符(j当然越大越好),现在需要检验 A[i+1] 和 B[j+1] 的关系。当 A[i+1]=B[j+1] 时,i 和 j 各加1;什么时候 j=m 了,我们就说 B 是 A 的子串(B串已经整完了),并且可以根据这时的i值算出匹配的位置。当A[i+1]<>B[j+1],KMP的策略是调整 j 的位置(减小j值)使得 A[i-j+1...i] 与 B[1…j] 保持匹配且新的 B[j+1] 恰好与 A[i+1] 匹配(从而使得i和j能继续增加)。我们看一看当 i=j=5 时的情况。
i = 1 2 3 4 5 6 7 8 9 ……
A = a b a b a b a a b a b …
B = a b a b a c b
j = 1 2 3 4 5 6 7
此时,A[6]<>B[6]。这表明,此时 j 不能等于5了,我们要把 j 改成比它小的值 j'。j' 可能是多少呢?仔细想一下,我们发现,j' 必须要使得 B[1..j] 中的头j'个字母和末j'个字母完全相等(这样j变成了j'后才能继续保持i和j的性质)。这个 j' 当然要越大越好。在这里,B [1..5]="ababa",头3个字母和末3个字母都是"aba"。而当新的j为3时,A[6] 恰好和 B[4] 相等。于是,i 变成了6,而 j 则变成了4:
i = 1 2 3 4 5 6 7 8 9 ……
A = a b a b a b a a b a b …
B = a b a b a c b
j = 1 2 3 4 5 6 7
从上面的这个例子,我们可以看到,新的 j 可以取多少与 i 无关,只与B串有关。我们完全可以预处理出这样一个数组P[j],表示当匹配到B数组的第 j 个字母而第 j+1 个字母不能匹配了时,新的j最大是多少。P[j]应该是所有满足 B[1..P[j]]=B[j-P[j]+1..j] 的最大值。
再后来,A[7]=B[5],i和j又各增加1。这时,又出现了A[i+1]<>B[j+1]的情况:
i = 1 2 3 4 5 6 7 8 9 ……
A = a b a b a b a a b a b …
B = a b a b a c b
j = 1 2 3 4 5 6 7
由于 P[5]=3,因此新的 j=3:
i = 1 2 3 4 5 6 7 8 9 ……
A = a b a b a b a a b a b …
B = a b a b a c b
j = 1 2 3 4 5 6 7
这时,新的 j=3 仍然不能满足 A[i+1]=B[j+1],此时我们再次减小 j 值,将 j 再次更新为 P[3]:
i = 1 2 3 4 5 6 7 8 9 ……
A = a b a b a b a a b a b …
B = a b a b a c b
j = 1 2 3 4 5 6 7
现在,i 还是7,j 已经变成1了。而此时 A[8] 居然仍然不等于 B[j+1]。这样,j 必须减小到 P[1],即0:
i = 1 2 3 4 5 6 7 8 9 ……
A = a b a b a b a a b a b …
B = a b a b a c b
j = 0 1 2 3 4 5 6 7
终于,A[8]=B[1],i 变为8,j 为1。事实上,有可能j到了0仍然不能满足 A[i+1]=B[j+1](比如A[8]="d"时)。因此,准确的说法是,当j=0了时,我们增加i值但忽略 j 直到出现A[i]=B[1] 为止。
这个过程的代码很短,我们在这里给出:
j:=0;for i:=1 to n dobegin while (j>0) and (B[j+1]<>A[i]) do j:=P[j]; if B[j+1]=A[i] then j:=j+1; if j=m then begin writeln('Pattern occurs with shift ',i-m); j:=P[j]; end;end;
最后的j:=P[j]是为了让程序继续做下去,因为我们有可能找到多处匹配。
这个程序或许比想像中的要简单,因为对于 i 值的不断增加,代码用的是for循环。因此,这个代码可以这样形象地理解:扫描字符串A,并更新可以匹配到B的什么位置。
附:
在KMP算法中,依据模式串的next函数值实现字串的滑动,若令next[j]=k;则next[j]表示当模式串中的Pj与主串中相应字符不相等时,令模式串的Pk与主串的相应字符进行比较。
求模式串的next函数:
void Get_next ( char *p, int next[ ] ) { int i = 0, j = -1, slen; slen = strlen(p); next[0] = -1; while ( i < slen ) { if ( j = -1 || p[i] == p[j] ) { ++i; ++j; next[i] = j; } else j = next[j]; }}
KMP模式匹配算法,设模式串第一个字符的下标为0:
int Index_KMP(char *s, char *p, int pos, in next[]){ int i = pos-1, j = -1, slen = strlen(s), plen = strlen(p);
while (i < slen && i < plen)
{
if(j = -1 || s[i] == p[j])
{
++i; ++j;
}
else
j = next[j];
}
if(j > plen)
return i-plen;