Java实现——KMP算法分析

KMP算法

KMP算法之所以叫KMP算法是因为这个算法是由三个人共同提出来的,所以取这三个人名字的首字母作为该算法的名字。

上一个博客讲了BF算法,但是已经有了BF算法来解决字符串匹配的问题,为什么还要研究出KMP算法呢?
KMP算法相对于BF算法,它有一定的优点,但是并不代表KMP算法可以完全替代BF算法,在一定场景下,BF算法应用的还是很广泛。下面将解释KMP的优点:

在BF算法中,如果下标i和j对应的字符不相等的时候,i要回退到起始位置的下一个位置,j回退到起始位置再开始进行匹配,这样一来,会浪费大量时间。

KMP算法提出:

下标i的位置不回退,j每次回退到特定的位置k

为了确定在匹配不成功时,变量j回退的位置,引入了next[]数组,next[]数组中存放的内容是:当前变量j的值(数组下标的位置)所回退到的位置的下标(这句话读起来有点拗口或者难以理解,在下文示例中会说明),即next[j] = k

此时有一个求K值的规则:
如果当前下标所表示的字符不匹配时,也就是说,之前的字符串都匹配成功,在匹配成功的字符串中寻找两个相等真子串(不包含本身),一个真子串以0下标开头,另一个真子串以j-1下标结尾。此时,所找到的真子串的长度则为k的值

注:

  1. “相等的真子串”应是:字符相同,且长度相同的两个真子串
  2. 上述规则只限于从1号下标开始不匹配的字符。如果第一个字符不匹配的话,将变量j回退到-1位置,因为i和j是同步走的,所以i走一步到1号位置时,j走一步到0号位置,这样才可以正确的匹配。
    在这里插入图片描述
    如图示例,j=5,在5号下标的位置匹配失败,按照规则:在主(子)串中5号位置之前寻找两个相等的真子串,“abcab”为匹配成功的字符串,其中相等的真子串为“ab”,第一个“ab”位置为01,第二个位置为34(4=j-1),长度为2,因此k值为2,变量j要回退的位置为2号位置,对应的字符为“c”,然后从i的位置和j的位置开始匹配字符。
    next[j]=k此时应为:next[5]=2,所以数组5号下标对应的值为2,表示,当j走到5号位置时,若不匹配,则回退到2号位置。

综上所述,KMP算法的思想是:

在匹配的过程中,若匹配失败,分两种情况:①第一个字符匹配失败,此时i位置不变,将j回退到-1位置。再开始重新匹配。②非第一个字符匹配失败,i位置不变,j回退到next[j]的位置继续进行匹配。

这时KMP算法已经很清楚,关键在于求next[]数组的值,有两种算法:

  1. 按照递推的思想:
    根据求k值的规则:next[0]=-1,
    在这里插入图片描述
    (如上图) 假设next[j]=k,即:p[0]…p[k-1] = p[x]…p[j-1],就是01“ab”==34“ab”,x=j-k即:p[0]…p[k-1] = p[j-k]…p[j-1]

①若p[j] == p[k] ,可知:p[0]…p[k-1]…p[k] = p[j-k]…p[j-1]…p[j] → p[0]…p[k] = p[j-k]…p[j],也就是说0-k的字符串和j-k到j的字符串相等。因此next[j+1] = next[j]+1 =k+1(如下图)
在这里插入图片描述
②若p[j]!=p[k],则可以看成普通的模式匹配问题,即匹配失败的时候,k=next[k]

根据得到的公式:
next[0] = -1;
next[1] = 0;
next[2] = 0;
next[3] = 0;
next[4] = 1;
next[5] = 2;
next[6] = 3;
next[] = [-1,0,0,0,1,2,3]

  1. 直接求解
    示例一
    求“ababcabcdabcde”的next[]数组:
    在这里插入图片描述
    解析:
    当第0个字符a不匹配时,j回退到-1,此时next[0]=-1;
    当第1个字符b不匹配时,字符b之前没有两个相同的真子串,所以j回退到0,此时next[1]=0;
    当第2个字符a不匹配时,字符a之前没有两个相同的真子串,所以j回退到0,此时next[2]=0;
    当第3个字符b不匹配时,字符b之前相同的真子串为“a”,长度为1,所以j回退到1,此时next[3]=1;
    当第4个字符c不匹配时,字符c之前相同的真子串为“ab”,长度为2,所以j回退到2,此时next[4]=2;
    当第5个字符a不匹配时,字符a之前没有两个相同的真子串,所以j回退到0,此时next[5]=0;
    当第6个字符b不匹配时,字符b之前相同的真子串为“a”,长度为1,所以j回退到1,此时next[6]=1;
    当第7个字符c不匹配时,字符c之前相同的真子串为“ab”,长度为2,所以j回退到2,此时next[7]=2;
    当第8个字符d不匹配时,字符d之前没有两个相同的真子串,所以j回退到0,此时next[8]=0;
    当第9个字符a不匹配时,字符a之前没有两个相同的真子串,所以j回退到0,此时next[9]=0;
    当第10个字符b不匹配时,字符b之前相同的真子串为“a”,长度为1,所以j回退到1,此时next[10]=1;
    当第11个字符c不匹配时,字符c之前相同的真子串为“ab”,长度为2,所以j回退到2,此时next[11]=2;
    当第12个字符d不匹配时,字符d之前没有两个相同的真子串,所以j回退到0,此时next[12]=0;
    当第13个字符e不匹配时,字符e之前没有两个相同的真子串,所以j回退到0,此时next[13]=0;
    next[] = [-1,0,0,1,2,0,1,2,0,0,1,2,0,0]
    示例二:
    求“ a b c a b c a b c a b c d a b c d e ” 的 next[]数组?
    next[] = [-1 , 0 , 0 , 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 0 , 1 , 2 , 3 , 0]
    解析:
    数组下标:
    0 k=-1
    1 k=0
    2 k=0
    3 k=0
    4 k=1:a a
    5 k=2:ab ab
    6 k=3:abc abc
    7 k=4:abca abca
    8 k=5:abcab abcab
    9 k=6:abcabc abcabc
    10 k=7:abcabca abcabca
    11 k=8:abcabcab abcabcab
    12 k=9:abcabcabc abcabcabc
    13 k=0
    14 k=1:a
    15 k=2:ab ab
    16 k=3:abc abc
    17 k=0
    示例三:
    求“ a b c a b a b c a b c ”的next数组?
    next[] = [-1 , 0 , 0 , 0 , 1 , 2 , 1 , 2 , 3 , 4 , 5]
    解析:
    next[4] =1 : a
    next[5] =2 : ab ab
    next[6] =1 : a
    next[7] =2 : ab ab
    next[8] =3 : abc abc
    next[9] =4 : abca abca
    next[10] =5: abcab abcab

next数组的优化→nextval数组:

如果字符串为"aaaaaaaab"时,求出其next数组为{-1 ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 }
当在第八个元素位置匹配失败,根据next数组,需要退回到第七个元素
由于第八个元素和第七个元素相等,那么需要继续退到第六个元素,接着退到第五个元素…这种方式会浪费很多不必要的时间,因此就涉及到next数组的优化:
如果当前的字符和回退后的字符相同的话,则当前nextval的值和回退后nextval的值相同,否则nextval的值为当前next的值

那么,“aaaaaaaab”的nextval数组为?

字符串aaaaaaaab
next-101234567
nextval-1-1-1-1-1-1-1-17

nextval[]= [-1,-1,-1,-1,-1,-1,-1,-1,7]

练习:“a b c a a b b c a b c a a b d a b”的nextval数组为?

字符串abcaabbcabcaabdab
next-10001120012345601
nextval-100-11020-100-1106-10

到这里,KMP算法就介绍完了,在学KMP之前,我看过别人的博客,还在百度上搜了很多,但是别人写的终归是别人的,还是要按照自己的理解写一遍,才知道KMP到底是怎样的思想。

代码实现:

/**
 * @auther: 巨未
 * @DATE: 2019/2/17 0015 9:44
 * @Description:  kmp算法java实现
 */

public class KMP {
   public static void getNext(String sub, int[] next) {  //得到next[]数组
        next[0] = -1;
        next[1] = 0;
        int j = 2;//下一项要求的 j 的值, next[j]   next[2]
        int k = 0; //前一项K的值
        while (j < sub.length()) {  //遍历子串
            if (k == -1 || sub.charAt(k) == sub.charAt(j - 1)) {  //k=-1,处于子串的起始位置||判断匹配失败位置的前一个字符串   和   匹配失败位置的前一个字符串的回退位置为下标的字符是否相等
                next[j] = k + 1;   //上述推导中得next[j+1] = k+1,上行if语句中判断的是j-1,所以此时应为next[j-1+1]= k+1;
                j++;
                k = k + 1; 
            } else {     //p[j]!=p[k]
                k = next[k]; //字符匹配失败,k回退。直到找到和p[j-1]相同的字符,停止回退。
            }
        }
    }

    public static int KMP(String str,String sub,int pos) {
        int lenstr = str.length();
        int lensub = sub.length();
        //对pos的位置合法判断,
        if(pos < 0 || pos > str.length()){
            return -1;
        }
        int i = pos;//主串从pos位置开始遍历
        int j = 0;//子串从0开始遍历

        int[] next = new int[lensub];  //next数组的长度和子串的长度一样
        getNext(sub,next); //获取next数组
      while( i < lenstr && j < lensub) { //遍历字符串的前提是i和j都小于字符串的长度
          if (j == -1 || str.charAt(i) == sub.charAt(j)) { //判断下标所对应的字符是否相等,j=-1,表示第一个字符不匹配,
              i++;
              j++;
          } else {   //匹配失败
              j = next[j];  //j会退到特定位置next[j],这个值数组中有存储
          }
      }
                if(j >= lensub) { //子串遍历完成,返回匹配成功时 i 的起始位置。
                    return  i-j;
                }else {   // 没找到
                    return  -1;
                }
            }
  public static void main(String[] args) {
        String str = "abcdadcdacd";
        String sub = "adc";
        
        int index = KMP(str,sub,0);
        System.out.println(key);
    }
}

实现结果:

在这里插入图片描述

由代码可以看出:KMP的时间复杂度为O(m+n)。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
KMP算法是一种字符串匹配算法,用于在一个文本串S内查找一个模式串P的出现位置。它的时间复杂度为O(n+m),其中n为文本串的长度,m为模式串的长度。 KMP算法的核心思想是利用已知信息来避免不必要的字符比较。具体来说,它维护一个next数组,其中next[i]表示当第i个字符匹配失败时,下一次匹配应该从模式串的第next[i]个字符开始。 我们可以通过一个简单的例子来理解KMP算法的思想。假设文本串为S="ababababca",模式串为P="abababca",我们想要在S中查找P的出现位置。 首先,我们可以将P的每个前缀和后缀进行比较,得到next数组: | i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | | --- | - | - | - | - | - | - | - | - | | P | a | b | a | b | a | b | c | a | | next| 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 | 接下来,我们从S的第一个字符开始匹配P。当S的第七个字符和P的第七个字符匹配失败时,我们可以利用next[6]=4,将P向右移动4个字符,使得P的第五个字符与S的第七个字符对齐。此时,我们可以发现P的前五个字符和S的前五个字符已经匹配成功了。因此,我们可以继续从S的第六个字符开始匹配P。 当S的第十个字符和P的第八个字符匹配失败时,我们可以利用next[7]=1,将P向右移动一个字符,使得P的第一个字符和S的第十个字符对齐。此时,我们可以发现P的前一个字符和S的第十个字符已经匹配成功了。因此,我们可以继续从S的第十一个字符开始匹配P。 最终,我们可以发现P出现在S的第二个位置。 下面是KMP算法的C++代码实现

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值