KMP子串匹配算法

KMP

KMP算法解决的问题
有两个字符串str1和str2,str1 是否包含str2,如果包含返回str2在str1中开始的位置。如何做到时间复杂度0(N)完成?

经典算法就是遍历str1的每个字符,以该字符为头去匹配str2,假如str1的长度为n,str2的长度为m,则时间复杂度为 O ( n m ) O(nm) O(nm)

最大前缀和

这个概念是用于要匹配的字符串,就是上面的str2。举例:str2=abcgcbaY ,对于Y,来计算其最大前缀和c:

前缀后缀是否相等
c=1aa
c=2abba
c=3abccba
c=4abcggcba
c=5abcgccgcba
c=6abcgcbbcgcba

假如Y前面有n个字符,那么c不会取到n来比较的,因为必然相等,所以没意义。由上表可知,Y的最大前缀和为1。针对需要匹配的字符串会开辟一个等长的next数组,用于存放对应字符的最大前缀和。

  1. next[0] 规定为 − 1 -1 1 因为str2中0位字符前面没有东西
  2. next[1]规定为 0 0 0 str2中1位字符前面只有1个字符,但是在计算最大前缀和时又不能取到整个前面字符串的长度,所以规定为0

str2=bgeddfbgeY 此时Y的最大前缀和为 3 3 3

算法流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EZcYfGPV-1662971253954)(/Users/ymy/Library/Application Support/typora-user-images/image-20220912113108456.png)]

假设str1和str2从 i i i 一直到 x − 1 x-1 x1 都和str2对应相等,但是 s t r 1 [ x ] ≠ s t r 2 [ y ] str1[x] \neq str2[y] str1[x]=str2[y] ,只有最后一个匹配不上。如果是经典方法,就会是i-->i+1 j-->0 然后继续匹配。KMP算法 总体路线也是这样,但是会利用最大前缀和加速,去除重复匹配。

n e x t [ y ] = c next[y]=c next[y]=c 表示y的最大前缀和为c,所以 s t r 2 [ 0 ] ~ s t r 2 [ c − 1 ] = s t r 2 [ y − c ] ~ s t r 2 [ y − 1 ] str2[0]~str2[c-1] = str2[y-c]~str2[y-1] str2[0]str2[c1]=str2[yc]str2[y1]

又因为 s t r 1 [ i + y − c ] ~ s t r 1 [ x − 1 ] = s t r 2 [ y − c ] ~ s t r 2 [ y − 1 ] str1[i+y-c]~str1[x-1]=str2[y-c]~str2[y-1] str1[i+yc]str1[x1]=str2[yc]str2[y1]

所以 s t r 1 [ i y − c ] ~ s t r 1 [ x − 1 ] = s t r 2 [ 0 ] ~ s t r 2 [ c − 1 ] str1[i_y-c]~str1[x-1]=str2[0]~str2[c-1] str1[iyc]str1[x1]=str2[0]str2[c1]

所以只需要让 i-->x j-->c 这样就省了很多匹配的步骤,达到了加速的效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QKgjQgA2-1662971253954)(/Users/ymy/Library/Application Support/typora-user-images/image-20220912120432720.png)]

问题:如何保证在 i ~ x − 1 i~x-1 ix1 中没有一个元素能和str2匹配上呢?i - -> x会不会漏掉呢?

用反证法来证明不存在漏掉的情况,假设在 ( i + c − 1 ) ~ ( x − c ) (i+c-1)~(x-c) (i+c1)(xc) 之间有一个 k k k,使得以 k k k 为头能和str2完全匹配上,那么就有:

s t r 1 [ k ] ~ s t r 1 [ x − 1 ] = s t r 2 [ 0 ] ~ s t r 2 [ x − 1 − k ] str1[k]~str1[x-1]=str2[0]~str2[x-1-k] str1[k]str1[x1]=str2[0]str2[x1k]

又因为: s t r 1 [ k ] ~ s t r 1 [ x − 1 ] = s t r 2 [ k − i ] ~ s t r 2 [ x − 1 − k − i ] str1[k]~str1[x-1]=str2[k-i]~str2[x-1-k-i] str1[k]str1[x1]=str2[ki]str2[x1ki] 此时的最大前缀长度超过了C,

所以矛盾,所以反证法成立。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c5Mjuvm8-1662971253955)(/Users/ymy/Library/Application Support/typora-user-images/image-20220912143113572.png)]

整个匹配的过程如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YHgz7Qzv-1662971253955)(/Users/ymy/Library/Application Support/typora-user-images/image-20220912145344559.png)]

求next数组

在这里插入图片描述

如果next[i]>next[i-1],则next[i]=next[i-1]+1,为什么?可以用反证法证明

假设 n e x t [ i − 1 ] = 2 ,    n e x t [ i ] = 4 next[i-1]=2, \;next[i]=4 next[i1]=2,next[i]=4

在这里插入图片描述

由图片可以看出在 n e x t [ i ] = 4 next[i]=4 next[i]=4 的情况下, n e x t [ i − 1 ] = 3 next[i-1]=3 next[i1]=3

算法实现
public class KMP {
   // 若des是ori的子串,返回匹配成功的索引;若不存在返回-1
   public static int getIndexOf(String ori, String des){
      if (ori == null || des == null || ori.length() < 1 || ori.length() < des.length())
         return -1;
      char[] source = ori.toCharArray();
      char[] template = des.toCharArray();
      // 获取匹配字符串的最大前缀和数组
      int[] next = getNextArr(template); // O(M)

      int i = 0, j = 0;
      while (i < source.length && j < template.length){ // O(N)
         if (source[i] == template[j]){
            i++;
            j++;
         } else if (next[j] == -1) { // 等价于 j == 0
            i++;
         } else {
            j = next[j];
         }
      }
      // i越界了或j越界了。上面while中,只有两个元素相等j才会往后移动,所以如果是j越界了就说明匹配成了
      return j == template.length ? i - template.length : -1;
   }

   private static int[] getNextArr(char[] template) {
      if (template.length == 1)
         return new int[] {-1};
      int[] next = new int[template.length];
      next[0] = -1;
      next[1] = 0;
      int i = 2, cn = next[i - 1];
      while (i < next.length){
         if (template[cn] == template[i - 1])
            next[i++] = ++cn;
         else if (cn > 0) {
            cn = next[cn];
         } else {
            next[i++] = 0;
         }
      }
      return next;
   }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值