KMP算法图片详解

简介:

KMP算法,解决字符串中的包含问题,时间复杂度O(M+N)。例:str1(长度为M)和str2(长度为N)为两个字符串,判断str1中是否包含str2。

解决办法:

暴力破解:

从开头开始一直向下配,如果配不上从下一个字符继续配,每次配对过程均独立。时间复杂度O(M*N)

KMP:

在介绍KMP算法前,首先声明一个概念——最长前缀与最长后缀的匹配长度:一个字符前面的字符串的最长前缀和最长后缀的最大匹配长度,并且前缀不能包含最后一个字符,后缀不能包含第一个字符。

例:abcabcd这个字符串,如果我求d的前面的字符串的最长前缀和最长后缀的最大匹配长度(即abcabc)
在这里插入图片描述
首先如果匹配长度是1
在这里插入图片描述
匹配长度为2:
在这里插入图片描述
长度为3:
在这里插入图片描述
同样长度为4时:abca与cabc不匹配
长度为5:不匹配。长度为6时,abcabc与abcabc虽然匹配,但是由于前缀包含了最后一个字符c,后缀包含了第一个字符a,所以不符合要求。所以d值为3。

然后求str2的这个信息。生成一个next数组,标记str2的每个位置的前面字符串的最长前缀与最长后缀的最大匹配长度(上面所说的概念),默认next[0]=-1,又因为规定最长前缀与最长后缀分别不能不能包含最后一个字符及第一个字符,所以规定next[1]=0,后面的可以由计算得出(先不用管怎么得到的,先知道由这个数组可以实现KMP,一会再计算next数组)。
例:str2对应的next数组的值
在这里插入图片描述
**KMP流程:**str1与str2的前面字符均匹配,直到x与y不匹配,此时我们之前已经得到str2的next数组,及前面的最长前缀与最长后缀的匹配长度,此时便不需要按照暴力方法从0位置开始与i+1位置重新进行匹配,只需使0与j相匹配,又因为前缀与后缀相等,所以直接匹配x与z
在这里插入图片描述
标红的四个子串是相同的,因为str1中红色部分与str2的最长后缀(就是第二个红色部分)相同,而str2中两个红色部分分别是最长前缀与最长后缀,所以相同,所以所有红色部分均相同。

实质:不能否认从j位置与0位置重新匹配不能成功,也就是从j位置开始与0位置相匹配,有可能匹配出str2,。但是可以确定从i到j不可能配出str2。
原因:
在这里插入图片描述
假设从str1的i到j之间存在一个位置k,可以往后配出str2,那么从k到x之前的绿色部分的字符串应该与str2的前面的绿色部分的字符串相等,而刚才说,str1与str2直到x与y部分才不匹配,那么str2与str1绿色部分对应的字符串应该相等。所以说str2中相匹配的最大前缀与最大后缀就从原来的红色部分变为绿色部分,这明显与所求的next中数值不同,所以在i到j之间不存在可以匹配出str2的位置k。

一个向后推2次的例子
在这里插入图片描述
str1与str2在D与F位置不匹配,F查找next数组中的对应值,找出最大前缀与最大后缀为abkab,然后str2平移后推一次,a与D匹配,仍然不匹配,查找a在next中的值,找出后缀为ab,再次后推。k与D仍然不匹配,而且k在next中值为0,则返回str2的0位置与str1下一个位置相匹配(str2的a与str1的D的下一个位置匹配

代码实现(java)

    public int getIndexof(String s,String m){
        if(s == null || m == null || m.length() < 1 || s.length() < m.length()){
            return -1;
        }
        char[] str1 = s.toCharArray();  //将java中字符串转换为字符数组,便于其他语言阅读
        char[] str2 = m.toCharArray();
        int i1 = 0;   //str1中索引
        int i2 = 0;   //str2中索引
        int[] next = getNextArray(str2);   //得到next数组
        while(i1 < str1.length && i2 < str2.length){
            if(str1[i1] == str2[i2]){    //str1与str2中的字符相匹配,继续判断下一个是否匹配
                i1++;
                i2++;
            }
            else{     //str1与str2中的字符不匹配
                if(next[i2] == -1)    //如果i2已经到str2的头部,即索引为0,那么str1中i1移到下一个字符
                    i1++;
                else
                    i2 = next[i2];    //如果i2还未移动到str2的头部,那么i2继续向前移动。移动到next[i2]的原因是:
                                      //因为str2的i2字符存在最长前缀与最长后缀,那么最长前缀部分不需要再次匹配,
                                      //直接从最长前缀的下一个位置与str1的i1匹配
            }
        }
        return i2 == str2.length ? i1-i2 : -1;
    }

next数组的计算
首先next[0]=-1,next[1]=0;对于后面的,数学归纳法得到;
对于任何位置i,i-1位置的next值已经求出,则只需判断i-1位置最长前缀的下一个位置字符x是否与i位置字符相匹配,如果匹配next[i]=next[i-1]+1,

例:如果x==b,则next[i]=next[i-1]+1=5在这里插入图片描述

在匹配的情况下,对于next[i]不可能存在比next[i-1]+1更大的值了,因为假设next[i] > next[i-1]+1,则很容易证明i-1位置的next值求错了。
如果x与b不匹配,找出x字符位置对应的next值,继续向前跳,找出x最长前缀的下一个字符与b比较,一直重复此过程,直到x==b,或者跳到开头。
在这里插入图片描述
ababcababtk这个例子求k对应的next值,就是向前跳两次的例子,并且k对应的next值为0.

代码实现(java):

    public int[] getNextArray(char[] str2){
        if(str2.length == 1){
            return new int[] {-1};
        }
        int[] next = new int[str2.length];
        next[0] = -1;
        next[1] = 0;
        int i = 2;   //要计算的位置索引
        int cn = 0;  //跳的位置,即x的位置,需要与i-1相匹配的位置
        while(i < next.length){
            if(str2[i-1] == str2[cn])    //匹配成功
                next[i++] = ++cn;
            else if (cn > 0)      //匹配不成功,但是x还可以向前跳
                cn = next[cn];
            else                  //x已经不能再向前跳了
                next[i++] = 0;
        }
        return next;

    }

应用:

1、在原始串上添加字符,使得新串满足:只能在后面添加字符,并且新串需要包含两个原始串,并且开头位置不一样。求最短新串(京东面试题)
例如:abcabc为原始串,添加后形成新串为abcabcabc,abcabcabcabc也是可以,但是不是最短。

求解:计算原串整体的next数组,并且在后面多加一个元素,即整个串的最大前缀与最大后缀的值,abcabcx中求x的next数值。

2、t1、t2两棵树,问t1的一个子树中是否完全包含t2。
在这里插入图片描述
包含。
在这里插入图片描述
不包含。

求解:把树序列化,得到可以唯一代表一棵树的节点值与结构的字符串。_代表值的结束,#代表节点为null。把t1、t2两棵树分别序列化成str1、str2两个字符串,如果str2是str1的一个子串,那么结果必然是包含。

求序列化例子:
在这里插入图片描述
3、如何确定一个字符串不是由一个范式重复得到的,比如abcabcabcabc由abc重复得到。

文章为“左神算法进阶班”笔记

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陌兮_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值