KMP(由Knuth,Morris,Pratt三个人发明)算法,时间复杂度为:
T
=
O
(
n
+
m
)
T=O(n+m)
T=O(n+m)
相比于暴力匹配的O(mn)有一定提高。
KMP算法的核心思想就是当发生失配时,则在前面已经匹配的部分中,找到最长的相同前缀,如下图的紫色和绿色部分,那么下次移位时直接将前面的前缀和后面对齐即可,从而不必每次只移动一位。这样string中的指针不会回溯。
为了得知当发生失配时我们要在子串中回退到哪里,我们需要针对pattern进行如下分析处理:
定义match函数构造next表:
m
a
t
c
h
(
j
)
=
{
满
足
p
0
.
.
.
p
i
=
p
j
−
i
.
.
.
p
j
的
最
大
i
(
<
j
)
−
1
,
如
果
这
样
的
i
不
存
在
match(j)= begin{cases} 满足p_0...p_i=p_{j-i}...p_j的最大i( 例如: 将其保存在match[]数组里。 递归地,当计算match[j]时,如果pattern[match[j-1] + 1] == pattern[j],则可以直接得到match[j]=match[j-1]+1。 如果没那么幸运pattern[match[j-1] + 1] != pattern[j],也不至于回到起点重开,可以根据match[j-1]的match值,向前找到匹配的部分(下面第一块绿色部分),则对于相同的后半段,最后的紫色部分和最开始的绿色部分一定是匹配的,再利用上面的步骤即可。 有了match[]数组,KMP算法的实现很简单: 定义s和p两个指针分别从头开始遍历主串和模式串,当二者都未到达结尾时执行如下循环: if (str[s] == pattern[p]),说明当前字符匹配,s和p同时向后移动; else if (p > 0),当前字符失配,且不是模式串的第0个字符,就是下图的平凡情况,则需要将p指针回溯到绿色部分末尾,也就是p-1的match值:p = march[p - 1] + 1;;
match(j)只与比较小的 pattern子串有关,保存了子串中一首一尾能够匹配的最长子串的信息,我们
第一个问号处是match[match[j-1]+1],第二个问号处当然是j。
否则说明模式串的第一个字符就不匹配,++s;#include
测试代码
void test_KMP() {
printf("n%sn", __func__);
char string[] = "This is a simple example";
char pattern0[] = "simple";
char pattern1[] = " isa";
char pattern2[] = "Th";
char pattern3[] = "e";
char pattern4[] = "exam";
char pattern5[] = "sample";
char* patterns[6] = {pattern0, pattern1, pattern2, pattern3, pattern4, pattern5};
int p;
printf("nstring:%sn", string);
for (int i = 0; i < 6; ++i) {
printf("npattern%d:%sn", i, patterns[i]);
p = KMP(string, patterns[i]);
if (p != -1) {
printf("%sn", string + p);
} else {
printf("Not foundn");
}
}
return;
}



