KMP算法是由Donald Knuth、James H.Morris和于1977年联合发明的,用来处理字符串匹配问题。
【题目描述】:对给定两个字符串str和substr,如果字符串str中含有子串substr,则返回substr在str中的开始位置,不含有则返回-1。
在介绍KMP算法之前,我们先来看普通解法(朴素模式匹配)怎么做。
最普通的解法是从左到右遍历str的每一个字符,然后看如果以当前字符作为第一个字符出发是否匹配出 substr,这里从数组下标为0开始。假设 str=“bcabcababcbabcabba”,substr=“abcabb”。
1.substr第0位字符不匹配,说明从str[0]出发进行匹配是不行的,后移1位,从str[1]出发进行匹配
b |
c | a | b | c | a | b | a | b | c | b | a | b | c | a | b | b | a |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
a |
b | c | a | b | b |
2.substr第0位字符不匹配,后移1位
b | c |
a | b | c | a | b | a | b | c | b | a | b | c | a | b | b | a |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
a |
b | c | a | b | b |
3.substr第5位字符不匹配,后移1位
b | c | a | b | c | a | b | a |
b | c | b | a | b | c | a | b | b | a |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
a | b | c | a | b | b |
4.substr第0位字符不匹配,后移1位
b | c | a | b |
c | a | b | a | b | c | b | a | b | c | a | b | b | a |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
a |
b | c | a | b | b |
如此循环,直到匹配成功
普通解法的时间复杂度较高,假设字符串str和substr长度分别为N和M,从每个字符出发时,匹配的代价都可能是O(M),一共有N个字符,所以整体的时间复杂度为O(N×M)。普通解法的时间复杂度这么高,是因为每次遍历到一个字符时,检查工作相当于从无开始,之前的遍历检查不能优化当前的遍历检查。
比如在第3步,我们已经知道"abcab"这5个字符是匹配的,而普通解法没有利用到这一事实,只是简单的将substr串后移;KMP算法思想就是利用这一事实,减少重复比较的次数,提高算法效率。
KMP算法是怎么实现的这一点呢?他在此使用了一个叫做 “部分匹配表” 的数据结构,如下图所示,这张表是怎样求得的在后面会讲到。
substr | a |
b |
c |
a |
b |
b |
---|---|---|---|---|---|---|
部分匹配值 | 0 | 0 | 0 | 1 | 2 | 0 |
在进行到第3步时,采用KMP算法,利用部分匹配表计算substr向后移动的位数:
3.substr第5位字符’b’不匹配,查表得到部分匹配值为0,说明str当前不匹配的位置应该与substr的第0位字符进行下一次匹配,即substr向后移动5位(=已匹配串"abcab"长度5 - 查表得到的部分匹配值0)
b | c | a | b | c | a | b | a |
b | c | b | a | b | c | a | b | b | a |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
a | b | c | a | b | b |
4.substr第3位字符不匹配,查表得部分匹配值为1,substr向后移动2位(=已匹配串"abc"长度3 - 查表得到的部分匹配