上一篇是BF算法,虽然BF算法是简单易懂,但是其中许多冗余的遍历,使得整个效率变低。而KMP算法可以用来有效剔除其中的多说无效遍历。KMP算法是一种改进的字符串匹配算法,由D.E.Knuth与V.R.Pratt和J.H.Morris同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。其中实现减少匹配次数的主要方法是提前算出模板的一个部分匹配表next[]。
先举个例子吧,文本是abcabcdabdabcdabc,模板是abcdabc,
模板的匹配数组next[]表示如下:
这个代表模板的前缀和后缀的一个最大值匹配表,后续详解。
匹配的过程如下:
第1次匹配,文本a位置匹配失败,则由k - next[k - 1]得到跳跃的间隔为3 - 0 = 3。
2次匹配,文本d位置匹配失败,则由k - next[k - 1]得到跳跃的间隔为6 - 2 = 4。
3次之后的匹配,与上述相同。
上述的过程,其实精华在于如何算出它的跳跃步数,想法是前缀和后缀匹配的最大值,从模板分析:
对于1个字母,无前缀后缀,所以为0;
对于2个字母,前缀为a,后缀为b,所以为0;
对于3个字母,前缀为{a,ab},后缀为{c,bc},无匹配,为0;
对于4个字母,前缀为{a,ab,abc},后缀为{d,cd,bcd,},无匹配,为0;
对于5个字母,前缀为{a,ab,abc,abcd},后缀为{a,da,cda,bcda},a匹配,长度为1,为1;
对于6个字母,前缀为{a,ab,abc,abcd,abcda},后缀为{b,ab,dab,cdab,bcdab},ab匹配,长度为2,为2;
对于7个字母,前缀为{a,ab,abc,abcd,abcda,abcdab},后缀为{c,bc,abc,dabc,cdabc,bcdabc},abc匹配,长度为3,为3;
到这里,分析完毕,下面附上KMP算法C++版:
#include <stdio.h>
#include <iostream>
using namespace std;
void getNextFrame(string needle, int* next){
int i = 0;
int j = 1;
int len = needle.length();
while(j < len)
{
if(needle[i] == needle[j])
{
i++;
next[j] = i;
j++;
}else{
i = next[0];
j++;
}
}
}
int KMPSearch(string haystack, string needle) {
int n = haystack.size() - 1;//haystack长度
int m = needle.size() - 1;//needle长度
if(m == -1) return 0;
if(m > n) return -1;
int next[needle.length()] = {0};
getNextFrame(needle, next);
int i = 0;
while(needle[0] != haystack[i])
i++;
int j = i;
int k = 0;
while(i <= haystack.length() - needle.length() && j < haystack.length())
{
if(needle[k] == haystack[j])
{
if(k == needle.length() - 1)
return i;
k++;
j++;
}else{
if(k == 0)
{
i = i + 1;
k = 0;
j = i;
}else{
i = i + k - next[k - 1];
k = 0;
j = i;
}
}
}
return -1;
}
int main(){
string haystack = "babbbbbabb";
string needle = "bbab";
int firstPos = KMPSearch(haystack, needle);
if(firstPos < 0)
cout<<"search failed\n"<<endl;
else
cout<<"first appeared place:"<<firstPos<<endl;
return 0;
}