KMP问题详解(牛奶)

1.什么是KMP算法?

KMP是一种改进后的字符串匹配算法。
例如判断字符串1中是否包含字符串2,如果使用暴力算法需要将字符串的每一个字符都一一进行对比,耗时耗力,时间复杂度是O(N*M)。N是比对次数,M是字符串2的长度。
在这里插入图片描述

而KMP算法体提供了另一种判断是否为子字符串的方法。可以将时间复杂度压缩到O(n)的级别。

首先理解前缀后缀
如下图:
abcabck中k的前缀为1就是a,后缀为1就是c,依次类推,前缀为2–ab,后缀为2–bc等。前缀就是该字母前面字符串的第一个字母开始起,后缀就是前面字符串的最后一个字母开始数。
最大匹配长度:前缀和后缀相等的最大长度。
在这里插入图片描述
因为该字符串的第一个字母前面什么都没有,所以它的最大区配长度为-1,第2个字母因为前面只有一个字母,但前缀和后缀不能相等,所以最大区配长度为0,**对于任意一个字符串,前两个字母的最大匹配长度都是-1和0;**着重看下第5个最大匹配度,我刚开始以为是5,其实不是,到3的时候前缀是aab, 后缀是baa就已经不满足了。注意前缀后缀看的顺序依然是从左往右。

把一个字符串的每一个最大匹配度组成的数组称为next数组。
前面字符串
匹配方法:
如下图,暴力解法中,字符串str1为原数组,str2为要匹配的数组,如果字符串str2匹配str1,从i开始都和str2相等,但是匹配到x发现不等于y了,那么就把x返回到i+1的位置,y返回到0的位置,重新进行匹配。
而KMP的巧妙之处在于,如果匹配到匹配到x不等于y了,x保持不动,y返回到自己next数组对应的位置,重新和x进行匹配。next[y]之前的字符默认和x之前的字符都相等!
在这里插入图片描述

原理:

注意是给子串建立一个next数组
如果上面的看不懂,看下面这个实例,字符串str2匹配str1,两个都是从0开始,但是str2匹配到下标为6的位置发现和str1不相等,然后它就会跳到下标为next[6]=3的位置重新和str1第6个位置匹配。如果还不相等,就会跳到该位置的next[3]的位置重新和str1的下标为6的位置进行匹配。

这种方法有两个隐含条件:
1.相当于str2从下标为3的地方重新和str1进行匹配,并且下标为3,4,5一定和str2下标为0,1,2,相等。

2.str1在下标为1,2的地方一定不能和str2匹配成功。
在这里插入图片描述

证明:

哎,证明不太好用语言解释啊。

证明第一个条件:
在这里插入图片描述
证明第二条:
在这里插入图片描述

    int* getNext(string p)
    {
        int* next = new int[p.length()];//开辟一段数组空间用来储存要对比的字符串
        next[0] = -1;           //固定的,第一个字符串的匹配度必须是-1
        int j = 0;				//副字符串的指针
        int k = -1;				//副字符串的next下标
        while (j < (int)p.length() - 1)
        {
        	//,或者正在对比的字母等于它的next[]
            if (k == -1 || p[j] == p[k])
            {
            	//指针向后移动
            	//next值加1,并赋给新的j!!!这里非常重要,不理解看链接
                j++;
                k++;
                next[j] = k;
            }
            else
            {
            	//如果此时的字母不等于它的next[]值,那就重新赋值k重新找,直到找到为止
                k = next[k];
            }
        }
        return next;
    }

上面代码我说说我的理解:
整体思想就是为了给副字符串寻找next数组,即给副字符串的每一个字母都找到它的最大匹配度。
那应该怎么找呢?最大匹配度意味着前缀和后缀相等时的最大长度。第0个字母的最大匹配长度一定是-1,所以next[0] = -1;k=-1;k是临时空间用来存储next值。
进入if语句,j++,k++,便开始第1个字母的填充了,它把第二个字母填充为next[1]=0;这个对于所有字符串一定成立的。接下来就是重点了,当进入下一轮while循环的时候,此时依然是遍历第二个字母的时候,k已经为0,所以if的第一个条件一定不成立,**第二个条件p[1]==p[0],它代表的是遍历到的该字母是否等于它最大字符匹配度处的字母,如果不等于,就让k=next[k],这时重新k=-1,重新进入if语句里面,但是j没有动,进入if后开始遍历下标为2的字母,因为前面k=-1,所以next[2]=0;下一轮还是下标为2的字母,k又重新变成了-1,在下一轮时,把下标为3的字母next[3]=0;
假设这个字符串是abca,但是再下一轮遍历,此时发现p[3]=p[k],

如果等于,那么下一个字母的最大字符匹配度就是该字母最大字符匹配度加1,**原因很简单,如下图,如果该字母和它的next位置相等,而因为next位置表示的就是该前缀等于后缀时,前缀后面的位置,所以对于下一个字母的next,就是在前一个next的基础上加1。
在这里插入图片描述
在这里插入图片描述
如果p[1]!=p[0],它代表的是遍历到的该字母不等于它最大字符匹配度处的字母,不等于就改变k接着找,这个改变很巧妙,不是k–,而是找next[k],如果找不到就把它的next赋为0,找到就变为k+1;

详细的KMP代码详解

完整算法:

    int KMP(string T,string p)
    {
        int i=0;
        int j=0;
        int* next=getNext(T);
        while (i < (int)T.length() && j < (int)p.length())
        {
            if (j == -1 || T[i] == p[j])
            {
                i++;
                j++;
            }
            else
            {
                j=next[j];
            }
        }
        if (j == (int)p.length())
        {
            return i-j;
        }
        return -1;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值