本文只是做一个简单的概括。
思想
KMP算法的思想用下面一张图就能说清楚:

在上图中,要检测T中是否包含P。
当匹配到某个位置发现两者不相等时(红框),肯定P要继续往后挪。普通的字符串匹配方法(也就是时间复杂度时m*n的那种),一般P只往后挪一个位置(也就是P'),然后从头开始和T比较。但是如图所示,当我们发现红框位置的不一样时,同时也代表P中红框前5个字母已经和T匹配上了,所以只有图中蓝色框部分的字母相同时,P'的比较才有意义,否则P'在前4个字母的比较时候就会匹配失败。
换句话说,当红框位置的匹配失败时,只有b之前的字母有相同的前后缀时,后移后的比较才有意义。再看下图

还是同一个字符串,其中b之前还真有相同的前后缀,也就是“ab”。此时我们可以断言,把P后挪3个位置时安全的,起码对于T中我们已经“看”过的字母,P和它匹配还好。
所以KMP算法的关键就是构建Failure Array(很多博客叫next数组)。next数组的长度和P长度相同,next[j]代表的含义是,在P[:j+1]中,最长的相同的前后缀长度是多少,如下图。next中的2表示P中截止到第5个字母为止的字串中,最长的前后缀是2。(注意,在具体实现时,有的人next的位置和我描述的刚好差1,如上面给的第2个参考文档中的实现)

LeetCode第28题就是实现字符串匹配,代码如下:
class Solution {
public:
vector<int> generate_next(string s){
int i = 0, j = 1;
vector<int> res(s.size(), 0);
while(j < s.size()){
if(s[i] == s[j]){
res[j++] = ++i;
} else if(i > 0){
i = res[i-1];
} else{
res[j++] = 0;
}
}
return res;
}
int strStr(string haystack, string needle) {
vector<int> next = generate_next(needle);
int i = 0, j = 0;
while(j < needle.size() && i < haystack.size()){
if(haystack[i] == needle[j]){
++i, ++j;
} else if(j > 0){
j = next[j-1];
} else {
++i;
}
}
if(j == needle.size()){
return i - needle.size();
} else{
return -1;
}
}
};
假如P的长度是m,T的长度是n,KMP算法的时间复杂度是O(m+n),其中构建next数组复杂度是O(m),匹配复杂度是O(n)。有一个很有趣的证明,以上述的代码为例:
- 构建next时,考虑2*j - i的大小,会发现它初始是0,最终最大是2m,且三个if-else分支不论走哪个,2*j-i都是递增的,所以最多训练2m次,也就是O(m).
- 匹配时也同理,考虑2*i-j的大小,可以得到同样的结论。
网友评论