内容会持续更新,有错误的地方欢迎指正,谢谢!
举例:有一个字符串”BBC ABCDAB ABCDABCDABDE”,我想知道,里面是否包含目标字符串”ABCDABD”?
别忘了,空格也是字符。Knuth-Morris-Pratt算法(KMP)是最常用的解决该类问题的算法。
蛮力字符串匹配
看到这题,我们的第一个想法是蛮力匹配:
- 先定义两个下标变量i和j分别表示原字符串和目标字符串的首字符下标;
- 若两个下标变量对应的字符相同,则一起往后走一步;
- 否则,原字符串的i=i-j+1,就指向了原字符串第一个A(见下图)的下个字符B;此时,j要重新置为0;
- 怎样判断匹配成功?当j==目标字符串的长度则返回true,否则返回false。
蛮力法效率很差,因为你要把 “搜索位置” 移到已经比较过的位置,重比一遍,而KMP用部分匹配表解决了这个浪费。蛮力字符串匹配的时间复杂度为 O(nm),其中 n 为原字符串的长度,m 为目标字符串的长度。很明显,这样的时间复杂度很难满足我们的需求。接下来进入时间复杂度为 O(n+m) 的KMP字符串匹配。
KMP字符串匹配
站在巨人的肩膀上,请见:字符串匹配的KMP算法
不过就是新加了几个概念:
- 前缀:除了最后一个字符外,一个字符串的全部头部组合。
- 后缀:除了第一个字符以外,一个字符串的全部尾部组合。
以”ABCDABD”为例:
“A”的前缀和后缀都为空集,共有元素的长度为0;
“AB”的前缀为[A],后缀为[B],共有元素的长度为0;
。。。
“ABCDAB”的前缀为[A][AB][ABC][ABCD][ABCDA],后缀为[B][AB][DAB][CDAB][BCDAB],最长的共有元素为”AB”,长度为2。 - 部分匹配值:前缀和后缀的最长的共有元素的长度。
- 部分匹配表:装部分匹配值,方便看呗。
(写代码的时候,第一个A的部分匹配值为-1,因为第一个字符就不匹配,就要后移一位)
当遇到不匹配的字符时,需移动的位数=已匹配的字符个数 - 最后一个匹配字符对应的部分匹配值
此时,已匹配的字符个数为6,最后一个匹配字符B对应的部分匹配值为2,那就移动4位,如下图:
KMP时间复杂度分析
得到部分匹配表,需要在目标字符串中遍历一次,时间复杂度为O(m)
利用部分匹配表在原字符串中遍历一次,就可完成字符串匹配,时间复杂度为O(n)
两者相加为 Θ(m+n),所以平均时间复杂度为 Θ(m+n)。
参考
【1】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/
【2】字符串匹配的KMP算法
http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html