——by m4trix
《柔性字符串匹配》: 《Flexible Pattern Matching in Strings》
KMP算法: Knuth-Morris-Pratt algorithm
Knuth is Donald Knuth, you know the guy I mean.
KMP算法: Knuth-Morris-Pratt algorithm
Knuth is Donald Knuth, you know the guy I mean.
所谓字符串匹配:给出两个字符串A,B,回答B串是否是A串的子串(A串是否包含B串)。这里称A为文本,B为模式,即要回答模式B是否为文本A的子串。
有:
文本 A = t1t 2t3...t n
文本 A = t1t 2t3...t n
模式 B = p1p 2p3...p m
传统的字符串匹配算法:(算法时间复杂度O((n-m+1)*m))
把文本A中位移分别0,1,...n-m时的文本(t1 t2t 3...tm, t 2t 3t 4...t m+1, t 3t 4t 5...t m+2, ...)依次跟模式B进行比较,查找匹配与否。
这种算法没有对已匹配过的信息加以利用,KMP算法就是在充分利用已匹配信息的基础上,来避免一些明显的不合理的移位(每次当t xt x+1t x+2...t x+m-1与模式B不匹配时,需要对文本A后移一位t x+1t x+2t x+3...t x+m-2来继续进行对比,其实有的时候可以一次移z(z>1)位,这样子效率更优)。那么问题来了:KMP是怎么来对已匹配信息加以利用的呢,每次不匹配了需要对文本移位再比较时,移多少位呢,怎么确定:
KMP算法的思想:(算法最坏时间复杂度O(n),平均时间复杂度O(n))
一些概念:
部分匹配表(Partial Match Table):所谓部分匹配表,即模式B中当匹配到第i个字符时,在p1p 2p3 ...p i中,既是该串的前缀、同时又是该串的后缀的最长字符串的长度与索引i的一个映射关系表。
部分匹配表(Partial Match Table):所谓部分匹配表,即模式B中当匹配到第i个字符时,在p1p 2p3 ...p i中,既是该串的前缀、同时又是该串的后缀的最长字符串的长度与索引i的一个映射关系表。
如有模式串:"abababca"
则有部分匹配表:
如何计算得来:
- "a"的前缀和后缀都为空集,因此公共元素的最大长度为0;
- "ab"有前缀["a"], 后缀["b"], 公共元素的最大长度为0;
- "aba"有前缀["a", "ab"], 后缀["ba", "a"], 公共元素"a"的最大长度为1;
- "abab"有前缀["a", "ab", "aba"], 后缀["bab", "ab", "b"], 公共元素"ab"的最大长度为2;
- "ababa"有前缀["a", "ab", "aba", "abab"], 后缀["baba", "aba", "ba", "a"], 公共元素"a", "aba"的最大长度为3;
- "ababab"有前缀["a", "ab", "aba", "abab", "ababa"], 后缀["babab", "abab", "bab", "ab", "b"], 公共元素"ab", "abab"的最大长度为4;
- "abababc"有前缀["a", "ab", "aba", "abab", "ababa", "ababab"], 后缀["bababc", "ababc", "babc", "abc", "bc", "c"], 公共元素的最大长度为0;
- "abababca"有前缀["a", "ab", "aba", "abab", "ababa", "ababab", "abababc"], 后缀["bababca", "ababca", "babca", "abca", "bca", "ca", "a"], 公共元素"a"的长度为1;
部分匹配长度(partial_match_length):所谓部分匹配长度,即文本A中的子串与模式B中的前i个字符相匹配,第i+1个字符不匹配,则此时的部分匹配长度为i.
有了部分匹配表和部分匹配长度,KMP算法对其怎么加以利用:
当文本A中的子串部分匹配模式B时,有部分匹配长度:partial_match_length, 部分匹配表PMTable, 则当PMTable[partial_match_length] > 1, 则文本A向前位移 partial_match_length - PMTable[partial_match_length - 1]长度,继续用子串与模式B来匹配,依次循环,直到匹配上或者直至文本A走完为止。
当文本A中的子串部分匹配模式B时,有部分匹配长度:partial_match_length, 部分匹配表PMTable, 则当PMTable[partial_match_length] > 1, 则文本A向前位移 partial_match_length - PMTable[partial_match_length - 1]长度,继续用子串与模式B来匹配,依次循环,直到匹配上或者直至文本A走完为止。
综上,KMP分两步来完成:
1、对模式串进行预处理,计算出部分匹配表;
2、依据Partial Match Table,对文本进行跳跃式位移来拿文本子串和模式进行匹配。
示例:
文本A:"bacbababaabcbab"
模式B:"abababca"
1、
如上图:
没有部分匹配中,文本A向前移1位;
2、
如上图:
部分匹配,有partial_match_length = 1,
根据:
if (PMTable[partial_match_length] > 1) {
文本A向前移partial_match_length - PMTable[partial_match_length - 1]长度;
} else {
文本A向前移 1 位;
}
PMTable[1] = 0 < 1,
文本A向前移1位;
3、
如上图:
没有部分匹配中,文本A向前移1位;
4、
如上图:
没有部分匹配中,文本A向前移1位;
5、
如上图:
部分匹配,有partial_match_length = 5,
PMTable[5] = 4 > 1, partial_match_length - PMTable[partial_match_length - 1] = 5 - PMTable[5 - 1] = 5 - 3 = 2,
文本A向前移2位;
6、
如上图:
部分匹配,有partial_match_length = 3,
PMTable[3] = 2 > 1, partial_match_length - PMTable[partial_match_length - 1] = 3 - PMTable[3 - 1] =3 - 1 = 2,
文本A向前移2位;
7、
如上图:
模式的长度已经超出了剩余的子文本串的长度了,因此匹配结束,没有匹配中。
模式的长度已经超出了剩余的子文本串的长度了,因此匹配结束,没有匹配中。
C代码实现:
KMP算法的适用场景:
因为KMP算法是对模式串进行预处理,因此该算法非常适合求解这样的问题:
给定一个模式串B,和一群文本A串,问B是哪些A串的子串。
<span style="font-size:18px;">/*
* input: x is the pattern string
* input: m is the length of pattern string
* output: KMPNext is the Partial Match Table
*/
void preKMP(char *x, int m, int KMPNext[])
{
int i, j;
i = 0;
j = KMPNext[0] = -1;
while (i < m) {
while (j > -1 && x[i] != x[j]) {
j = KMPNext[j];
}
i++;
j++;
if (x[i] == x[j]) {
KMPNext[i] = KMPNext[j];
} else {
KMPNext[i] = j;
}
}
}
void KMP(char *x, int m, char *y, int n)
{
int i, j, KMPNext[XSIZE];
/* Preprocessing */
preKMP(x, m, KMPNext);
/* Searching */
i = j = 0;
while (j < n) {
while (i > -1 && x[i] != y[j]) {
i = KMPNext[i];
}
i++;
j++;
if (i >= m) {
//OUTPUT(j - i);
i = KMPNext[i];
}
}
}</span>
KMP算法的适用场景:
因为KMP算法是对模式串进行预处理,因此该算法非常适合求解这样的问题:
给定一个模式串B,和一群文本A串,问B是哪些A串的子串。
Refer:
"The Knuth-Morris-Pratt Algorithm in my own words"
http://jakeboxer.com/blog/2009/12/13/the-knuth-morris-pratt-algorithm-in-my-own-words/
http://jakeboxer.com/blog/2009/12/13/the-knuth-morris-pratt-algorithm-in-my-own-words/
"KMP算法详解"
"Knuth-Morris-Pratt algorithm description and C code by Christian Charras and Thierry Lecroq"
http://www-igm.univ-mlv.fr/~lecroq/string/node8.html
http://www-igm.univ-mlv.fr/~lecroq/string/node8.html