KMP算法---关于next数组最详细的解答

        一切都是最好的安排,但是我们为了实现最好的安排,每一步都要拼尽全力。

我们在一个母字符串中查找一个子字符串有很多方法。KMP是一种最常见的改进算法,它可以在匹配过程中失配的情况下,有效地多往后面跳几个字符,加快匹配速度。

kmp算法的精髓就在于next数组,从而达到跳跃式匹配的高效模式。




而next数组的值是代表着字符串的前缀与后缀相同的最大长度。

"前缀"指除了最后一个字符以外,一个字符串的全部头部组合;

"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。

在KMP算法中有个数组,叫做前缀数组,也有的叫next数组,每一个子串有一个固定的next数组,它记录着字符串匹配过程中失配情况下可以向前多跳几个字符,当然它描述的也是子串的对称程度,程度越高,值越大,当然之前可能出现再匹配的机会就更大。

这个next数组的求法是KMP算法的关键,但不是很好理解,我在这里用通俗的话解释一下。

1.举一个Next数组的例子,注意next()方法里面求的就是这个数组。 

下面来说明这个数组的求法:

2.当前面字符的前一个字符的对称程度为0的时候,只要将当前字符与子串第一个字符进行比较。这个很好理解啊,前面都是0,说明都不对称了,如果多加了一个字符,要对称的话最多是当前的和第一个对称。比如agcta这个里面t的是0,那么后面的a的对称程度只需要看它是不是等于第一个字符a了。

3.按照这个推理,我们就可以总结一个规律,不仅前面是0呀,如果前面一个字符的next值是1,那么我们就把当前字符与子串第二个字符进行比较,因为前面的是1,说明前面的字符已经和第一个相等了,如果这个又与第二个相等了,说明对称程度就是2了。有两个字符对称了。比如上面agctag,倒数第二个a的next是1,说明它和第一个a对称了,接着我们就把最后一个g与第二个g比较,又相等,自然对称成都就累加了,就是2了。

4.当然不可能会那么顺利让我们一直对称下去,如果遇到下一个不相等了,那么说明不能继承前面的对称性了,这种情况只能说明没有那么多对称了,但是不能说明一点对称性都没有,所以遇到这种情况就要重新来考虑,这个也是难点所在。

重点:

         1.next数组的每个值的含义:在某个数组下标处,以数组下标处的字母结尾的字符串,前缀与后缀的最大重合长度。比如next[13]=7,就是在agctagcagctagc这个串中最大重合长度为7。     

         2. 如果想要计算next[14]的值,此时就需要寻求next[13]的帮助(知道前一位的最大重合长度,在前缀最大重合长度的基础上再多取一个字母即多取一个下标为7的字母,在后缀最大重合长度的基础上再多取一个字母即多取下标为14的字母,比较这两个字母是否相等,如果相等,则在next[13]的基础上+1,如果不相等见4)。

        3.但是因为数组是从0开始的,取这个最大重合长度,自然就定位到最大重合的下一个字符,所以直接取next[13]=7的7这个值,去找s[7],此时实际是第8个字符。

        4.(难点**) 如果不相等时也不直接去第一个字符处比较(确实不相等s[14]!=s[7]),相当于重复了在2的操作,在s[7]处,将a换成t(将s[7]换成了s[14])继续找s[7]处的最大重合长度(此时需要寻找next[6]的帮助),不断循环,直到无法找到重合子串,即到第一位与首字母a进行比较。

void makeNext(char s[],int next[])
{
    int len = strlen(s);
    next[0]=0;                    //初始化
    for(int i=1,k=0;i<len;i++)
    {
        //这个while是最关键的部分,k就是前一位的最大重复个数,即next[i-1]
        while(k>0 && s[k]!=s[i]) //s[k]!=s[i]即完成s[i]和s[next[i-1]]的比较,
                                 //不相等,则需要进入循环不断往前追溯。即步骤4
            k=next[k-1]; 
            //等价于  k=next[next[i-1]-1]
            //等号右边的k起的只是下标的作用
        if(s[k]==s[i])            
            k++;                  //相等就+1
        next[i]=k;                //赋值
    }
}
 

至此,KMP算法的Next数组求完。

KMP全部代码如下

public class UseString {


    //有了Next数组,现在来写字符串匹配算法吧。
    public int KMP(String s1,String s2){
        //求的模式串的Next数组
        int i=0,j=0;
        int[] next=next(s2);
        for (int s:next
        ) {
            System.out.println(s);
        }
        //Next数组输出完毕
        for (;i<s1.length()&&j<s2.length();i++){
             //回退到相等的地方。这个j和前面next数组的K有什么关系?
            //在求next数组的时候,k是前一位匹配的最大重合长度,chars[k]!=chars[i]是比较在前一次的最长前缀的新增后一位,和加入新的元素的最长后缀的新添加一位是否一样,不一样则需要回退。这个k和i都是一个字符串的指针。
            //这里的j是正在和主串匹配的长度,能匹配到j,说明前j-1个元素和主串能进行匹配,当这一位不满足时,需要找前一位的最长重合长度。
            while (j>0&&s1.charAt(i)!=s2.charAt(j)){
                j=next[j-1];
            }
            if (s1.charAt(i)==s2.charAt(j)){
                j++;
            }

            if (j==s2.length()){
                return i-j+1;
            }
        }
        return -1;
    }

    public int[] next(String s){
        //获取字符数组
        char[] chars=new char[s.length()];
        for (int i=0;i<s.length();i++){
            chars[i]=s.charAt(i);
        }
        //得到next数组
        int[] next=new int[s.length()];
        next[0]=0;
        //k是前一位的最大重合个数
        for (int i=1,k=0;i<next.length;i++){
            //这块有些反逻辑,新手想不出来的。为什么这个while循环要在前面呢?
            //和判断条件有关系,
            //你是从第一个数往后想,第一个匹配然后第二个呢?
            //但是从整体上来讲,你应该是这样的:
            //找出这个数的前一个的最大重合子串,看最大重合子串,和这个数是否相等
            //如果不相等则回退,一直回退到相等或者没有相等的即此时k=0,跳出这个循环
            while (k>0&&chars[i]!=chars[k]){
                k=next[k-1];
            }
            if (chars[i]==chars[k]){
                k++;
            }
            next[i]=k;

            //判断循环结束的条件:为什么要这个循环呢?就是往next里面跳,不断的匹配最短的跳
            // 1.k值等于0(到了最前面)
            // 2.这个不匹配,要往前跳

        }
        return next;

    }



    public static void main(String[] args) {
        String test1="adaagctagcagctagctg";
        String test2="agctagcagctagctg";
        UseString useString=new UseString();

        System.out.println(useString.KMP(test1, test2));


    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值