以下是自己整理的KMP算法,更厉害更全面的KMP算法可参见大牛们的博客。
大牛1:http://blog.csdn.net/v_july_v/article/details/7041827#
大牛2:http://blog.csdn.net/yaochunnian/article/details/7059486
1、普通字符串匹配BF算法与KMP算法的时间复杂度比较
KMP算法是一种线性时间复杂的字符串匹配算法,它是对BF算法(Brute-Force,最基本的字符串匹配算法的)改进。对于给的原始串S和模式串P,令|S|=n,|P|=m,需要从字符串S中找到字符串P首次出现的位置的索引。
BF算法的时间复杂度O(n*m),空间复杂度O(1)。KMP算法的时间复杂度O(m+n),空间复杂度O(m),因为KMP算法需要预处理模式串。具体说明,见文章最后。
2、KMP算法思想
普通的字符串匹配算法(如(BF)必须要回溯。但回溯就影响了效率,回溯是由P串本身的性质决定的,是因为P串本身有前后“部分匹配”的性质。像上面所说如果主串为abcdef这样的,大没有回溯的必要。
改进的地方也就是这里,我们从P串本身出发,事先就找准了P自身前后部分匹配的位置,那就可以改进算法。
如果不用回溯,那模式串下一个位置从哪里开始呢?
还是上面那个例子,P(模式串)为ababc,如果c失配,那就可以往前移到aba最后一个a的位置,像这样:
...ababd...
ababc
->ababc
这样i不用回溯,j跳到前2个位置,继续匹配的过程,这就是KMP算法所在。这个当P[j]失配后,j 应该往前跳的值就是j的next值(模式串P向右滑动的值),它是由P串本身固有决定的,与S串(主串)无关。
3、next数组的含义
重点来了。下面解释一下next数组的含义,这个也是KMP算法中比较不好理解的一点。令原始串为: S[i],其中0<=i<=n;模式串为: P[j],其中0<=j<=m。nextval数组也称k数组,用于指导模式串中使用几号元素进行后续的匹配。
下面给出nextval数组的get_nextval函数的正确代码:(综合了一些作者的实现,也许跟其他方法有些不同)
void get_nextval(const char * p, const int plen, int *nextval)
{
int i=0;
nextval[i]=-1;
int j=-1;
while (i<plen-1)
{
if (-1==j || p[i]==p[j])//循环的if部分
{
++i;
++j;
nextval[i]=j;
}
else //循环的else部分
j=nextval[j];
}
return;
}
如:模式P=“ababc”,使用get_nextval函数后所得的nextval为:nextval=“-1 0 0 1 2”
4、利用求得的next数组各值运用Kmp算法
Ok,next数组各值已经求得,万事俱备,东风也不欠了。接下来,咱们就要应用求得的next值,应用KMP算法来匹配字符串了。还记得KMP算法是怎么一回事吗?容我再次引用下之前的KMP算法的代码,如下:
int kmp_search(const char *src, const int slen, const char *patt, const int plen, const int *nextval, int pos)
{
//输入:src, slen主串
//输入:patn, plen模式串
//输入:nextval, KMP算法中的next函数值数组
int i=pos;
int j=0;
while(i<slen && j<plen)
{
if(j==-1 || src[i] == patt[j])
{
++i;
++j;
}
else
{
j=nextval[j];
//当匹配失败的时候直接用p[j_next]与s[i]比较
//下面阐述怎么求这个值,即匹配失效后下一次匹配的位置
}
}
if(j>=plen)
return i-plen;
else
return -1;
}
5、KMP算法的具体实现
根据上文中的解析,完整写出KMP算法的代码已经不是难事了,整理如下:
#include <iostream>
using namespace std;
void get_nextval(const char * p, const int plen, int *nextval)
{
int i=0;
nextval[i]=-1;
int j=-1;
while (i<plen-1)
{
if (-1==j || p[i]==p[j]) //循环的if部分
{
++i;
++j;
nextval[i]=j;//改进前
/*
if(p[i]!=p[j])//改进后
nextval[i]=j;
else
nextval[i]=nextval[j];
*/
}
else //循环的else部分
j=nextval[j];
}
return;
}
int kmp_search(const char *src, const int slen, const char *patt, const int plen, const int *nextval, int pos)
{
//输入:src, slen主串
//输入:patn, plen模式串
//输入:nextval, KMP算法中的next函数值数组
int i=pos;
int j=0;
while(i<slen && j<plen)
{
if(j==-1 || src[i] == patt[j])
{
++i;
++j;
}
else
{
j=nextval[j];
//当匹配失败的时候直接用p[j_next]与s[i]比较
//下面阐述怎么求这个值,即匹配失效后下一次匹配的位置
}
}
if(j>=plen)
return i-plen;
else
return -1;
}
int main()
{
/*
char *src="aabababcababcd";
char *patt="ababc";
*/
char *src="baba";
char *patt="ababc";
/*
char *src = "aabcabcebafabcabceabcaefabcacdabcab";
char *patt = "abce";
*/
const int slen=strlen(src);
const int plen=strlen(patt);
int *nextval=new int[plen];
get_nextval(patt, plen, nextval);
cout<<"nextval:"<<endl;
for(int i=0;i<plen;++i)
cout<<nextval[i]<<" ";
cout<<endl;
int index = kmp_search(src, slen, patt, plen, nextval, 0);
if(index!=-1)
cout<<"The first index of patt is: "<<index<<endl;
else
cout<<"not found matching!"<<endl;
delete []nextval;
return 0;
}
注:再次分析,KMP算法的时间复杂度O(m+n),当n>>m时,可视为θ(n),空间复杂度为θ(m)。具体分析见《算法导论》。
继续努力,O(∩_∩)O哈哈~