KMP算法浅析

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[ij..i1]=P[0..j1]P[0..k1]=P[jk..j1]T[ik..i1]=P[0..k1]
​ 公式能看明白就可以了,辅助作用。

几个需要解决的问题

  • 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算法的理解,或许今后会继续更新,吧……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值