KMP学习记录

注:模式串指我们要找是不是出现的串,主串指我们匹配的对象。

简介:

一种由Knuth(D.E.Knuth)、Morris(J.H.Morris)和Pratt(V.R.Pratt)三人设计的线性时间字符串匹配算法。

大致原理:

其实就是利用已经匹配过的字符,也就是在中间的每一个位置,就断开了。利用前面的部分去设置,下次寻找的点。

普通的就是这个失败,就返回刚开始的位置的下一个。继续寻找。不断的,不断的。。。

KMP的话,就是看你找过且对的上的部分的规律,通过这个规律,我们判断下一次找的点的位置,从模式串里面的那个位置开始。

比如下面的一个例子:

00000001
001     

当1不匹配的时候,我们是返回第二个位置接着寻找呢?(当然KMP也是这样的,但是我们指只要匹配不成功,就下面一个位置继续,重新开始的那种普通匹配。)

0000001
 001   

以上的寻找,怎么样改善呢?

 

 

KMP关键点:next数组

他通过判断制定一个next的规则,进行模式串下一次的位置判断点。就可以直接判断。从而实现O(n+m)的时间复杂度。

 

next的重点解析:

重点:每一个next的值,都是表示前看,后看的最长公共长度。  (以该点后看,和从开始往后看)

注:0特殊注意一下,1表示如果不相等,要返回的位置 = 1 -1=0,第一个0表示边界,后面的表示返回的位置(下标从1开始)

 

解释一下我的next的意思:

就几个例子,解释一下。好吧。

abcdefg这个例子:next为0 1 1 1 1 1 1;

aaaaa这个例子:next就是 0 1 2 3 4 了。

(最后一个a的next为4,表示如果不相等就要返回i = 3的位置区)

 

next的讲解:

我举一个例子:假设条件是 子串: 0001,主串:00000001.

00000001
0001    

现在的情况是:匹配到了j = 4的时候,二者不相同。那么我们应该让j跳到哪里去呢?(普通寻找和KMP的最大区别点)

普通寻找:j = 0,(从头开始继续),KMP:观察结束的位置,观察已经匹配好了的串的规律。

由题目可以看出:000 已经匹配好了的。那么我是不是可以认为这样呢?

00000001
0001    

前面 vision[0~j-1]是已经匹配好了的,说明如果我从匹配好了的里面找出"折点",是不是就可以不用从新开始了呢?

折点:在以匹配的串中,找出循环点的位置,(从开始寻找和自己点往前寻找比如abaab,折点在第三个a上面)

这个较特殊:我们换一个 子串:abaabc 主串:abaabaabc这个来找这个折点

abaabaabc
abaabc   

我们观看到i = j = 5的时候断了。这个时候,我们就要分析了折点在哪?

比如不存在折点问题:那么我想问你换一个位置重新匹配可以成功吗?答案显然是不存在的。

比如 abcdef,在f断了。那也表示着abcde匹配成功了,还表示着如果移一个位置重新开始,从b,c,d,e,f开始接着寻找可能找到吗?答案:不可能。

回到上面的问题:寻找折点:abaaab匹配成功了,折点在第一个b上面。

前面后面看只有ab了。考虑第三个的话,前面为:aba,后面:aab,不相等。所以在b上面。

那么我们下次寻找就不需要从新寻找,只需要从b的后面一个开始接着匹配就可以了。

 

时间复杂度与作用分析:

next差不多也就是讲完了。KMP的玄机也就是这样了。

当我们知道这些next,又有什么用呢?

当然有用了,结合next数组,在加上主串的寻找我们可以实现O(n+m)的时间。也就实现了时间复杂度的缩小的作用了。

还可以通过next反复在主串中寻找重复的子串,(记录位置,判断个数,最长前缀等等问题)

 

代码版署:

要想重复的搜索,有多少个。

只需要找到之后让k = 0.重新找。(if(k == len) k = 0,cnt++;)


我的代码是学校的oj,以0为开头的KMP算法,有可能看不懂,不过没关系,看得懂前面的就可以了。(next[i],表示的是0到i-1的位置的相同长度!0特殊注意一下,1表示如果不相等,要返回的位置)

 

#include <iostream>
#include <stdio.h>
#include <string.h>

using namespace std;

void get_next(char *str,int len,int *next)
{
    int k;
    next[0] = 0;
    k = 0;
    for (int i=1; i < len; i++)
    {
        while(k > 0 && str[i] != str[k])
            k = next[k-1];
        if (str[i] == str[k])
            k++;
        next[i] = k;
    }
}

void print_next(int *next,int len)
{
    for(int i=0; i<len; i++)
        printf("%d ",next[i]);
    printf("\n");
}


int KMP(char *str,int slen,char *vision,int len,int *next)
{
    int j = 0,k=0;
    for(int i=0; i<slen; i++)
    {
        while(j>0 && str[i] != vision[j])
            j = next[j -1];
        if(str[i] == vision[j])
            j++;
        if(j==len)
        {
            k ++;
            j = next[j-1];
        }
    }
    return k;
}

int main()
{
    char str[1000005],vision[10005];
    int slen,len,next[10005],y;
    scanf("%d",&y);
    while(y--)
    {
        scanf("%s%s",&vision,&str);
        len = strlen(vision);
        slen = strlen(str);
        get_next(vision,len,next);
        printf("%d\n",KMP(str,slen,vision,len,next));
    }
    return 0;
}

 

代码版署,也就结束了。学校的测试过了。不过我希望博友们,可以留言,指正一下可能的错误点或者测试用例。

 最后留言:

       其实,我就是听我们老师讲了一节课,自己的总结。怕忘了。

      最后,谢谢,各位大佬的观看!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值