KMP算法浅解
算法背景
KMP算法解决的问题是字符匹配,是由Knuth–Morris–Pratt共同开发出来的,这个算法把字符匹配的时间复杂度缩小到O(m+n),而空间复杂度也只有O(m),n是target的长度,m是pattern的长度,在此算法在发明之前并不是没有如此高效的算法,但是原算法比较复杂。Kmp算法优雅高效,但是实现却不难理解且代码长度很短,是优秀算法设计的典范,值得拿出来仔细分析;
算法核心
在最简单的枚举移动算法中,我们可以看到;逐步匹配的复杂度是
重点解疑
常见的问题是为什么跳转表能实现这个功能,为什么这么准确,它到底怎么实现的,还可以怎么理解。
关于这个问题,可以自己模拟一下去体会,可以发现这个规律。
注意事项
数学的来看一下定义,比如对于序列
而没有更大的k满足这个条件,就是说要找到尽可能大k,使pattern前k字符与后k字符相匹配,k要尽可能的大,原因是如果有比较大的k存在,而我们选择较小的满足条件的k,那么当失配时,我们就会使pattern向右移动的位置变大,而较少的移动位置是存在匹配的,这样我们就会把可能匹配的结果丢失.
计算这个跳转表的方法可以采用递推,可以想象如果对于pattern的前j个字符,如果覆盖函数值为k
则对于pattern的前j+1序列字符,则有如下可能:
⑴ pattern[k+1]==pattern[j+1] 此时overlay(j+1)=k+1=overlay(j)+1
⑵ pattern[k+1]≠pattern[j+1] 此时只能在pattern前k+1个子符组所的子串中找到相应的next函数,h=next(k),如果此时pattern[h+1]==pattern[j+1],则next(j+1)=h+1否则重复(2)过程.
inline void BuildNext(const char* pattern, size_t length, unsigned int* next) {
unsigned int i, t;
i = 1;
t = 0;
next[1] = 0;
while(i < length + 1) {
while(t > 0 && pattern[i - 1] != pattern[t - 1]) {
t = next[t];
}
++t;
++i;
if(pattern[i - 1] == pattern[t - 1]){
next[i] = next[t];
}else{
next[i] = t;
}
}
//pattern末尾的结束符控制,用于寻找目标字符串中的所有匹配结果用
while(t > 0 && pattern[i - 1] != pattern[t - 1]){
t = next[t];
}
++t;
++i;
next[i] = t;
}
跳转表的工作
假如:A=”abababaababacb”,B=”ababacb”,我们来看看KMP是怎么工作的。我们用两个指针i和j分别表示,A[i-j+ 1..i]与B[1..j]完全相等。也就是说,i是不断增加的,随着i的增加j相应地变化,且j满足以A[i]结尾的长度为j的字符串正好匹配B串的前 j个字符(j当然越大越好),现在需要检验A[i+1]和B[j+1]的关系。当A[i+1]=B[j+1]时,i和j各加一;什么时候j=m了,我们就说B是A的子串(B串已经整完了),并且可以根据这时的i值算出匹配的位置。当A[i+1]<>B[j+1],KMP的策略是调整j的位置(减小j值)使得A[i-j+1..i]与B[1..j]保持匹配且新的B[j+1]恰好与A[i+1]匹配(从而使得i和j能继续增加)。
直接实现
KMP理解了跳转表,基本上这个过程就很前清晰了;后期关于图示法描述出来。
程序代码
#include<iostream>
#include<string>
#include<vector>
using namespace std;
int kmp_find(const string& target,const string& pattern){
const int target_length = target.size();
const int pattern_length = pattern.size();
int * overlay_value = new int[pattern_length];
overlay_value[0] = -1;
int index = 0;
for(int i=1;i<pattern_length;++i){
index = overlay_value[i-1];
while(index>=0 && pattern[index+1]!=pattern[i])
index = overlay_value[index];
if(pattern[index+1]==pattern[i])
overlay_value[i] = index +1;
else
overlay_value[i] = -1;
}
//KMP match algorithm really start ; up is for next[]
int pattern_index = 0;
int target_index = 0;
while(pattern_index<pattern_length&&target_index<target_length){
if(target[target_index]==pattern[pattern_index]) {
++target_index;
++pattern_index;
}
else if(pattern_index==0)
++target_index;
else
pattern_index = overlay_value[pattern_index-1]+1;
}
if(pattern_index==pattern_length)
return target_index-pattern_index;
else
return -1;
}
int main(){
string source = "abcdefghijklmn";
string pattern = "cdef";
cout<<kmp_find(source,pattern)<<endl;
return 0;
}