首页 > 代码库 > KMP算法
KMP算法
KMP算法,是由Knuth,Morris,Pratt共同提出的模式匹配算法,其对于任何模式和目标序列,都可以在线性时间内完成匹配查找,而不会发生退化,是一个非常优秀的模式匹配算法。但是相较于其他模式匹配算法,该算法晦涩难懂,第一次接触该算法的读者往往会看得一头雾水,主要原因是KMP算法在构造跳转表next过程中进行了多个层面的优化和抽象,使得KMP算法进行模式匹配的原理显得不那么直白。
下面从最简单的字符串匹配算法来介绍KMP算法的原理。
例如:匹配下面的字符串
目标串:babcbabcabcaabcabcabcacabc
模式串:abcabcacab
简单的字符串匹配原理:
简单的字符串匹配算法用一个循环来找出所有有效位移,
该循环对n-m+1个可能的每一个s值检查条件P[1....m]=T[s+1....s+m]。
匹配次数 | 已匹配过的字符串 | 匹配的字符串 | 待匹配的字符串 |
第一趟 | b | abcbabcabcaabcabcabcacabc | |
a | bcabcacab | ||
第二趟 | b | abcb | abcabcaabcabcabcacabc |
abca | bcacab | ||
第三趟 | ba | b | cbabcabcaabcabcabcacabc |
a | bcabcacab | ||
第四趟 | bab | c | babcabcaabcabcabcacabc |
a | bcabcacab | ||
... | ... | ... | ... |
第六趟 | babcb | abcabcaa | bcabcabcacabc |
abcabcac | ab | ||
... | ... | ... | ... |
第十六趟 | babcbabcabcaabc | abcabcacab | c |
abcabcacab |
如上面显示的匹配过程,每次匹配失败,它会从原位置的下一个位置开始再重新匹配。
但是,实际上中间有许多重复的匹配,比如第六趟匹配失败后,会从"bcabcaa"开始匹配,这样b和c肯定匹配失败,而且后面一个"abc"也因为最后的"aa"而匹配失败,这和第六趟匹配失败是一样的原因,所以后面这些匹配实际上都是重复的。
如何去挑重复的匹配呢?这就可以用到KMP算法。
上面的情况可以看出来,重复匹配是因为回溯的时候每次都回溯到开始位置了。但实际上,这是不必要的。第六趟里面,实际上匹配失败后应该从"abcabcaa"中最后的‘a‘开始匹配。
这样想的话,只要能知道每次回溯的时候最好是回溯到什么位置,这样就不会有重复的匹配了。
那么最好回溯到的位置和什么有关系呢?
实际上它是和模式串本身相关,和目标串没有关系的。
例如:
现在我用模式串自身和它匹配来说明;
首先,第2个字母和第一个字母比较:
abcabcacab
a bcabcacab
显然,不匹配;如果此时,模式串和目标串匹配到第二个字母不匹配的时候,应该回溯到什么地方呢?
ac abcecacabed
ab cabcacab
应为模式串中b开始的子串不能和模式串匹配,所以是从模式串的起始位置开始;
那么,看看下面的情况,模式串的第4个字母开始比较:
abca bcacab
abc abca cab
它是有四个字母想匹配的,如果此模式串和目标串匹配到第七个字母不匹配的时候应该回溯到什么地方呢?
abcabcb acecacabed
abcabca cab
这个时候应该是从第四个字母开始重新比较;
abc abcb acecacabed
abca bcacab
和上面模式串匹配的结果相同。所以实际上回溯的位置是直接和模式串相关的。
KMP算法的过程:
KMP算法核心就是要计算模式串每个位置匹配失败后要回溯最佳的位置,这样才能保证没有重复匹配,达到线性的复杂度。
所以下面主要介绍next[]的求解过程;(pattern表示模式串,next[i]表示模式串第i个字符匹配失败时,模式串需要回溯,重新比较的字符位置坐标)
1.next[0] = 0;
2.如果求位置i+1的next数组值,要检查pattern[i] =?= pattern[next[i]];
如果pattern[i] == pattern[next[i]],next[i + 1] = next[i] + 1;(这种情况表示匹配成功,可以比前一个回溯多一个位置)
如果pattern[i] != pattern[next[i]],则比较pattern[i] == pattern[next[i - 1]];(这种情况则匹配next的前一个位置的值,看能否匹配,可以匹配的话就是该位置的值 + 1)
3.当pattern[i] != pattern[next[0]]时,next[i] = 1;
所以上面模式串的结果如下:
j | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
pattern[j] | a | b | c | a | b | c | a | c | a | b |
next[j] | 0 | 1 | 1 | 1 | 2 | 3 | 4 | 5 | 1 | 2 |
得到上面next数组后,重新参照next来匹配上面的目标串,过程如下:
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
b | a | b | c | b | a | b | c | a | b | c | a | a | b | c | a | b | c | a | b | c | a | c | a | b | c |
a | b | c | a | b | c | a | c | a | b | ||||||||||||||||
a | b | c | a | b | c | a | c | a | b | ||||||||||||||||
a | b | c | a | b | c | a | c | a | b | ||||||||||||||||
a | b | c | a | b | c | a | c | a | b | ||||||||||||||||
a | b | c | a | b | c | a | c | a | b | ||||||||||||||||
a | b | c | a | b | c | a | c | a | b | |
具体实现参照:http://www.cnblogs.com/yeqluofwupheng/p/6796747.html
参考:http://blog.csdn.net/power721/article/details/6132380
http://blog.csdn.net/joylnwang/article/details/6778316/
http://blog.csdn.net/yutianzuijin/article/details/11954939/
KMP算法