KMP算法
KMP算法(俗称“看毛片”),名字取自它的设计者的名字首字母,即Knuth(D.E.Knuth)、Morris(J.H.Morris)和Pratt(V.R.Pratt) ,它是一种线性时间的字符串匹配算法,可以用于求解字符串s2在字符串s1中出现的次数、位置。
Lead:从暴力算法谈起
既然是暴力算法嘛,那就来个暴力点的。比如下面两个字符串T与P,我们要求P在T中出现的位置。举个带有字符的栗子:
T:A B C A A B A A B C
P:A B C
那么我们可以在串T中寻找与P[0]相同的字符,然后截取P.size()位进行字符串比对,如果比对成功,那么就从输出它的首字母的位置。
这个算法很容易就可以想到,但根据我们的经验,很容易想到的算法效率一定很低。在最坏情况下,我们很容易得出有O(nm)O(nm)O(nm).明显需要优化。
Focus:KMP算法
KMP的思想
这个算法巧妙地运用了已知条件,避免了很多次没有必要的比较与枚举,我们先来看一个栗子:
0 1 2 3 4 5 6 7 8 9 10 11 12
T: a b c a c a b a b c a b
P: a b c a b
对于这两个字符串,我们假设两个指针:i指向T[0]在轴上的坐标,j指向P[0]在轴上的坐标。我们可以先进行比对,我们发现在1、2、3、4位置上这两个字符串是匹配的,但在位置5上时,发现了C与D并不匹配。这个时候,我们的i与j并不像第一种算法一样退回到起点,而是选择将这个串P向右移动3位,直接从第四位开始进行比对,这样就节省了很多时间。
就是变成
0 1 2 3 4 5 6 7 8 9 10 11 12
T: a b c a c a b a b c a b
P: a b c a b
对于这个字符串P的匹配成功的长度,即a b c a段,我们发现因为第一位与最后一位相同,所以我们可以直接把第一位移动到最后一位,然后进行比对,这个算法的可行性,似乎很容易看出来吧……
那么以此类推,对于匹配成功的串a b c a b段来说,因为前两位与后两位相同,所以我们可以把第一位移动到倒数第二位进行比对;
对于串a b a a b a,我们发现第一位等于最后一位,但是前三位也等于后三位。这时候我们需要做出一个决策:秉着不能漏找的原则,我们必须选取相等位数较多的那一组字串。所以我们需要从第四位开始进行比对。
总结一下,我们就会发现如下的移位法则:
当T[i]≠P[j]时有T[i−j..i−1]=P[0..j−1]有P[0..k−1]=P[j−k..j−1]必然有:T[i−k..i−1]=P[0..k−1]
当T[i]\ne P[j]时\\
有T[i-j..i-1]=P[0..j-1]\\
有P[0..k-1]=P[j-k..j-1]\\
必然有:T[i-k..i-1]=P[0..k-1]
当T[i]̸=P[j]时有T[i−j..i−1]=P[0..j−1]有P[0..k−1]=P[j−k..j−1]必然有:T[i−k..i−1]=P[0..k−1]
公式能看明白就可以了,辅助作用。
几个需要解决的问题
-
1.我们需要在初始化的时候建立一个失配数组,来帮助我们快速找到下一个位置而不是在循环中枚举,可以大大降低复杂度。但注意了(划重点),我们的失配数组需要建立在短的那个字符串的基础上。
-
2.如何确定失配后下一次比对的位置捏?自然是利用上述移位法则来建立一个数组,记录需要枚举几位。
-
3.我们暂且将这个数组称之为kmp(其实也被叫做next)。这个数组该如何得到呢?其实它就是自己与自己匹配的过程。举个栗子:
对于串a b c a b,我们需要一个第零位,来保护它不越界。并且我们不考虑字串本身,即只考虑它的真前缀和真后缀相同的最大值。
代码实现
我们该如何建立一个kmp数组呢?
j=0;
for (int i=2;i<=lb;i++)
{
while(j&&b[i]!=b[j+1]) //此处判断j是否为0的原因在于,如果回跳到第一个字符就不用再回跳了
j=kmp[j]; //通过自己匹配自己来得出每一个点的kmp值
if(b[j+1]==b[i])j++;
kmp[i]=j;//i+1失配后应该如何跳
}
这个应该是流传的最广的版本了。在这个代码中,我们通过自己与自己匹配,求得kmp数组的值。这个代码其实是死代码,码农们只要背下来就好了。
剩下的匹配就和上方雷同了,代码如下:
j=0;//你可以理解为j表示模式串匹配到第几位了
for(int i=1;i<=la;i++)
{
while(j&&b[j+1]!=a[i])j=kmp[j];//回跳
if (b[j+1]==a[i]) j++;//成功
if (j==lb)
{
cout<<i-lb+1<<endl;
j=kmp[j];
}
}
这段其实也是死代码,背下来就安啦。
Application
据我所知,这玩意儿运用的最广的地方就是各大网站的脏字匹配系统,这可以快速找到脏字的位置并用“?”来代替,并可以累积次数,次数多了就可以封号什么的……
好了以上就是我对kmp算法的理解,或许今后会继续更新,吧……