从最直观的逻辑看一下KMP算法

有一个文本串S,和一个模式串P,现在要查找P在S中的位置,怎么查找呢,KMP算法就是一种方法,看下图:

如果使用暴力匹配 ,假如S(x)-S(k)对应P(0)-P(k-x)的部分都相同(此处约定:S(n)代表字符串S中第n个字符,-代表到的意思,并非减),但是S(k+1)不等于P(k+1-x),则需要继续比较S(x+1)是否等于P(0),真的只能这样比较么,当然不是。

看KMP:假如在字符串S中S(x)-S(y)和S(z)-S(k)相同(此时S(x)-S(y)就是在字符串S(x)-S(k)中的最大长度的相同前缀后缀),如果S(k+1)不等于P(k+1-x),只需要比较P(y+1-x)和S(k+1)是否相等即可。接下来证明为什么在此区间不会有可以匹配成功的可能:

假如S(x)-S(z)中存在从P开头一直相等到S(k)的字符串,设为S(n),则S(n)-S(k)必须完全等于P从开头的等长部分,所以S(x)-S(k)中的最大长度的相同前缀后缀就应该是S(n)-S(k),而不是S(z)-S(k),所以不成立。

证明完事了,说一下算法,我从网上粘贴了一个获取next数组的算法(next数组是啥可以看这篇文章https://www.cnblogs.com/zhangtianq/p/5839909.html,很长,我只是觉得其中next数组说的很详细,别的我也没看0.0):

public static int[] getNext(String ps) {

    char[] p = ps.toCharArray();

    int[] next = new int[p.length];

    next[0] = -1;

    int j = 0;

    int k = -1;

    while (j < p.length - 1) {

       if (k == -1 || p[j] == p[k]) {

           next[++j] = ++k;

       } else {

           k = next[k];

       }

    }

    return next;

}

对比着下图理解上边逻辑:

第一次循环k=-1,j=0,next[1]=0,没啥说的,假设ps的前n个字符都不同,根据代码推算一下你就知道,他们对应的next数组中的值都会被赋予0,而k就会在-1和0中变化,j是字符串下标,是一直在增加的。

当首次出现等于字符串首字符(上例中为A)时,即p[j] == p[k](k=0),之后k和j都自增,当出现第一个不相等的字符时,看图,当k=2,j=4时,此时k应该回溯到什么位置:

注意,这是重点!!!

此时ABA和ABC已经失匹配,那k回到哪才可能继续匹配呢,答案是ABA这个字符串去掉最后一个字符(A)之后,所得到的字符串中最大长度的相同前缀后缀的前缀串的下一个字符的位置。这句话有点绕,看例子说明:

当j到C的下标位置时,ABA和ABC已经失匹配,k应该回溯到AB这个字符串中最大长度的相同前缀后缀的前缀串的下一个字符的位置,也就是第三个字符A对应的next数组中的值,即0;当j到D的下标位置时,ABABC和ABABD已经失匹配,k应该回溯到第一个ABAB中最大长度的相同前缀后缀的前缀串的下一个字符的位置,也就是C对应的next数组中的值,即2。

还是绕口的话,这么说吧,当j到D的下标位置时,ABABC和ABABD已经失匹配,如果D还有可能匹配,它一定是匹配第一个AB之后的这个字符,因为之前匹配过的串(ABAB)中AB是最长的前缀后缀相同的串,AB已经不用再匹配了,所以用D跟第三个字符A比较即可,当然不相等,然后k会变成next[2]也就是0,之后的逻辑就很清楚了。

其实这个逻辑很简单,就是遇到一个字符不匹配了,那我必须去找之前匹配过的最长前缀后缀的串的下一个字符跟他比较,如果还是不一样,继续在这个最长前缀中找最长前缀后缀的那个串,如此反复,缕缕就好了,这个想通了之后,真的挺简单的。

如果有理解错误,恳请指正,谢谢

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值