算法与数据结构——KMP(Java)(b站左程云课程笔记总结)

KMP

kmp算法解决的问题

字符串str1和str2,str1是否包含str2,如果包含返回str2在str1中开始的位置,并且要求做到时间复杂度为O(N)

注:str2必须为str1的连续子串

暴力解法:尝试str1的每一个开头

极端例子

image-20220621130055115

这种情况时间复杂度就比较高,此时时间复杂度为O(n*m)

KMP的过程与暴力解决的过程相似,但是过程有优化(加速),返回的结果的是str2在str1中开始的位置,如果不包含就返回-1

字符k前面的字符串的前缀和后缀

k带的信息是前缀和后缀相等的最大长度(长度为前缀总长度的时候不计算)

image-20220621130639750

str2的所有字符都需要此信息

如果一个字符前面没有字符串就人为规定为-1

字符串str2的0位置信息为-1,1位置的信息为0

image-20220621131201769

image-20220621132202556

按照暴力递归的思路:一开始从str1的i位置开始,从str2的0位置开始,一直匹配,直到匹配不成功,此时i来到了s字符,按照原来暴力递归的思路,此时应该i回到i+1位置,str2还是0,再重新开始匹配

但KMP怎么操作:i来到s字符之后,停住,str2从nextArr数组中获取字符z的信息,得到结果为6,str2从索引为6即字符s开始,二者再从当前的两个位置开始重新匹配

两件事情:

  1. str1的j位置到s之前的这段字符串与str2的相同位置是相等的,而从nextArr的原理可以得知前缀和后缀相等,即此时str1从s字符开始,str2从索引为6的字符s开始匹配,前面的一段字符串是相等的,减少比较的次数(直接来到可能不相等的位置)

  2. 如何证明从str1的最开始的i位置到j之前的任何一个字符开始都没法与str2匹配?

    使用反证法,假设i到j位置之间的任意一个位置为k(j位置是根据第一个不匹配的字符的最长前缀和后缀相等的数量往回推出来的),假设str1从k位置开始可以把整个str2匹配出来,即str1从k到x之前的字符串与str2从0到相等数量的字符串是一样的,而经过第一次的匹配可以得知str1从k到x之前的字符串是与str2相同位置是一样的(即得到y信息的后缀),而如果这两个条件都成立,则说明y的信息有变(nextArr数组的信息正确),最长前缀和后缀相等的字符串数量变长了,而这是不可能发生的,因此假设失效,即str1从i到j位置的任意一个位置k都不能把str2整个匹配出来

image-20220621133616978

如果一直匹配到str2不能再往前了都没法匹配成功,str1的比对位置往下一个

getNextArray方法:

第一位是-1

第二位是0

第三位根据前两位是否相等来判断

后面的根据前面的计算结果来判断:(dp)

  1. 此时到达的位置是否与前一位的信息(数量),即前缀的下一位是否相等(标记该位置为k),相等则+1就是这一位的信息
  2. 不相等的话看k位的前缀的下一位,判断是否相等,相等的话就是k位的信息+1,如果还是不等就一路往前跳,跳到最前面的话信息就是0,(注意这里还是要比较第一个字符和此时的字符是否相等,是的话返回1,不相等的话再返回0)
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();
    char[] str2=m.toCharArray();
    int i1=0;
    int i2=0;
    int[] nextArr=getNextArray(str2);//O(M)
    while(i1<str1.length&&i2<str2.length){//O(N),m<n,所以整个kmp算法的时间复杂度为O(N)
        if(str1[i1]==str2[i2]){
            i1++;
            i2++;
        }else if(i2==0){//nextArr[i2]==-1
            i1++;
        }else{
            i2=nextArr[i2];
        }
    }
    //i1越界或者i2越界了  i2越界代表str1的某个开头匹配出整个str2了  i1越界就代表整个str1没有能匹配出str2的
    return i2==str2.length?i1-i2:-1;//i1-i2即str1的索引回到q
}

public int[] getNextArray(char[] ms){
    if(ms.length==1){
        return new int[]{-1};
    }
    int[] next=new int[ms.length];
    next[0]=-1;
    next[1]=0;
    int i=2;//当前位置
    int cn=0;//拿哪个位置和当前位置比较,也代表当前使用的信息是多少
    while(i<next.length){//这里的时间复杂度估计和主方法中的while一致
        if(ms[i-1]==ms[cn]){
            next[i++]=++cn;
        }else if(cn>0){//当前跳到cn位置的字符,和i-1位置的字符匹配不上
            cn=next[cn];
        }else{
            next[i++]=0;
        }
    }
    return next;
}

怎么估计时间复杂度

人为的分析两个变量

两个变量最大的变化幅度就是循环发生的次数

三个循环分支对应的两个变量的变化程度如图

两个变量变化的最大幅度是2N的幅度,而且三个分支都没有让这两个变量下降的情况,所以循环的次数就和这两个变量变化的幅度绑定在一起

所以整个while循环的次数不会超过2N次

即时间复杂度为O(N)

image-20220621142607420

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值