写下这篇文章,希望能够帮助新手快速入门,加深理解。废话不多说,进入正题。
1.一些声明
首先定义一些符号来简化文章
Problem A 代表了 【判断一个字符串W是否在一个超大字符串T内出现过】 这个问题。
LenW,LenT 分别代表W和T字符串的长度
2.为什么选择KMP
在算法竞赛中,传统的暴力匹配算法在面对【Problem A】时,n^2 的时间复杂度不能满足时间要求。因此我们需要一种能够 快速解决Problem A 的算法,而KMP算法恰好能够解决这个问题。
3.什么是KMP
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度O(m+n)。—---百度百科。
4.KMP的具体过程
以Problem A为例,我们假设给出的字符串
W为:acbac
T为 :aababacbacdb
在暴力匹配时我们最坏情况下会将T的第(0~LenT-lenW)个字符依次作为起点,每次判断LenW次,这样的复杂度是LenW*LenT的。 但是,在KMP算法中,我们首先会对W字符串做一些O(LenW)操作,将每次判断次数都变为常数级别。
具体操作就是,求一个Next数组,数组内的第i个数据代表从s[0]到s[i]的一个特殊子串的length,这个特殊子串要满足三个条件:
①此子串是W的一个前缀
②此子串是W的一个后缀
③此子串尽量长
例如:aabca满足以上三个条件的子串是ac
求Next数组的具体过程O(LenW): 中间过程不给出,直接给出代码与注释,建议利用简单的串模拟一下中间过程,这样可以加深理解。
void Get_Next()
{
int j = 0, k = -1;
Next[0] = -1;
while(j < len_w)
{
if(k == -1 || W[j] == W[k])
{
Next[++j] = ++k;
}
else
{
k = Next[k];
}
}
}
得到Next数组后,匹配就很简单了,失配时直接向前找当前失配位置的Next,W串从Next数组保存的位置开始判断,这样在O(n+m)时间内就能解决Problem A
int Match()
{
int sum = 0;
getNext();
for(int i = 0 , j = 0; i < LenT ; i++)
{
while(j > 0 && W[j] != T[i]) j = Next[j];
if(W[j] == T[i]) j++;
if(j == LenW)
{
sum++;
j = Next[j];
}
}
return sum;
}