KMP算法

【学完这个之后,我感觉,,好多算法名都是人名啊】

参考两篇神级讲解,安利一波:

https://www.cnblogs.com/yjiyjige/p/3263858.html

https://blog.csdn.net/starstar1992/article/details/54913261/


解决问题:

  KMP算法是用来处理字符串匹配问题的,也就是说,当给你两个字符串,你需要回答B串是否是A串的子串或者是A串是否包含B串

  我们称等待匹配的A串为主串(母串)用来匹配的B串的为模式串。通常解决这个问题是用来枚举A串的位置与B匹配,然后验证是否匹配;假如说A串的长度为n,B串的长度为m,算法的复杂度则为O(nm),多数情况下枚举的复杂度是可以接受的,但也会有最坏的情况,下面介绍一种在最坏的情况下,时间复杂度人仍为O(n)的算法之一,KMP算法。

主要思路:

  充分利用模式串的自身的性质,在与主串匹配字符出现匹配失败的情况时,移动最大的距离,节省时间。(并非失败后一个一个字符的向后挪)【看不明白就瞅这句话,我基本上就是被这句话点透的,这句话就是整个算法的方向目的】

 【这东西我看一下午一上午刚明白,尽量说清楚吧】

  1》原始匹配方法:(上方母,下方子串)

                             

       从子串的第一个字符开始匹配,前三个匹配顺利,到第四个:

                             

       出现一颗老鼠屎,我们会这样移动子串:

                               

      然后再循环从子串的第一个开始匹配,直至到主串末尾。

2》上述方法当然不错,朴素,符合我蒟蒻的气质,但是大神们是忍受不了这样的遍历的:如果你遇见了四位置的老鼠屎,你会只向后移动一个位置吗?

      最起码我会将子串移动到下一个“A”上,再进行遍历,大神们依然不能忍受,你就只会挪与子串的第一个字符相同的位置,不会挪到与前两个都相同的吗?不会挪到前三个相同的吗?不会挪到与前四个。。(直至我弱弱的说,人家子串就四个字符。。);

     这个被嫌弃的过程就是算法的过程,当出现字符串不匹配时,大神们是这样做的:

                              

      即:字符A与字符B不同,整个子串就需要后移,移动到哪里呢,肯定整个子串不会移动到字符A的后面去(最多移动到字符A的位置,想想为嘛),即

      在已经遍历过的位置上寻找和 子串的前 k 个字符相同的位置(如图,字串中 前缀区域 3 和 母串中已经遍历过的 区域 2 是相同的,就将 子串挪 到 区域3 与 区域2 刚好匹配的位置,再进行判断)

     看出蹊跷了吗?在已经遍历过的区域找与子串某前缀相同区域,既然已经遍历过并且没有发现老鼠屎,那么主串中该区域一定和字串中的老鼠屎“B”之前的某一区域也匹配(即区域4);【有点绕,好好悟一下】;

     现在可以得到结论,在子串的老鼠屎之前,有一段前缀和后缀相同,则将前缀直接移动到后缀位置;若没有这样的前后缀,则整串移动到位置A处,开始新的旅程。【注意是子串的“老鼠屎”之前,老鼠屎在子串爆发的位置是不可预测的】

  3》问题是,谁都不知道在匹配的过程中,谁变成了老鼠屎,子串从位置0到 n 都有可能不匹配,怎么知道究竟移动到哪里?

       这就是另一个重要问题,要先对子串的含有前k个字符的子串的前缀后缀相同的 “缀”的大小 进行打表,方便在子串的任意位置不匹配时,可以直接移动前缀到后缀的位置;

     例如:

考察目标字符串ptr: 
ababaca 
这里我们要计算一个长度为m的转移函数next。

next数组的含义就是一个固定字符串的最长前缀和最长后缀相同的长度。

比如:abcjkdabc,那么这个数组的最长前缀和最长后缀相同必然是abc。 
cbcbc,最长前缀和最长后缀相同是cbc。 
abcbc,最长前缀和最长后缀相同是不存在的。

**注意最长前缀:是说以第一个字符开始,但是不包含最后一个字符。 
比如aaaa相同的最长前缀和最长后缀是aaa。** 
对于目标字符串ptr,ababaca,长度是7,所以next[0],next[1],next[2],next[3],next[4],next[5],next[6]分别计算的是 
a,ab,aba,abab,ababa,ababac,ababaca的相同的最长前缀和最长后缀的长度。由于a,ab,aba,abab,ababa,ababac,ababaca的相同的最长前缀和最长后缀是“”,“”,“a”,“ab”,“aba”,“”,“a”,所以next数组的值是[-1,-1,0,1,2,-1,0],这里-1表示不存在,0表示存在长度为1,2表示存在长度为3。这是为了和代码相对应。
 

代码实现:

KMP的代码实现包括两部分,一个是子串与主串的判定移动,另一个子串的打表,代码实现我现在很弱,先粘上大佬代码:

预处理子串:

p[1]=0;
j=0;
for(int i=1;i<m;++i){
    while(j>0&&b[j+1]!=b[i+1])j=p[j];
    //不能继续匹配且j还没减到0,考虑退一步;
    if(b[j+1]==b[i+1])++j;//能匹配,j的值加1;
    p[i+1]=j;
}

两串匹配:

j=0;
for(int i=1;i<n;++i){
    while(j>0&&b[j+1]!=a[i+1])j=p[j];
    //不能继续匹配且j还没减到0,减小j的值
    if(b[j+1]==a[i+1])++j;//能继续匹配j,j的值加1;
    if(j==m){//找到一处能匹配
        printf("%d\n",i+1-m+1);//子串串首在母串的位置
        j=p[i];//继续寻找匹配值
    }
}

  可以说是比较坑了,我说我是从BFS过来的你信吗,,最短路径输出->哈希表->字符串hash->KMP,,我要重新投入最短路的怀抱!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值