coding A&D:KMP算法

一些算法、数据结构长时间不用,就忘记了,甚至连概念都忘了,最麻烦的是忘记当初如何理解该概念的方法了。。。

浏览博客时发现这两位博主的博文有助于我重新理解kmp算法,遂参考一下,进行总结,有助于以后使用:

MandW:https://blog.csdn.net/u011564456/article/details/20862555

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

 

总结如下:

【概念】:首先KMP是用来进行串的模式匹配的算法之一。串的模式匹配:给你两个字符串,寻找其中一个字符串是否包含另一个字符串,如果包含,返回包含的起始位置。

KMP的改进在于:每当一趟匹配过程中出现自负比较不等时(失配位),不许回溯i指针,而是利用已经得到的“部分匹配”的结果将模式串向右“滑动”尽可能远的一段距离后,继续进行比较。

例如:

这里写图片描述

 

【时间复杂度】:KMP算法可以实现复杂度为:O(m+n)

【注意】:用next数组,next数组是以下标0开始的!

【匹配过程】:

假设在我们的匹配过程中出现了这一种情况:
 

 

 

 

蓝色是匹配成功的部分,红色是失配位:即匹配失败了

根据KMP算法,在该失配位会调用该位的next数组的值!在这里有必要来说一下next数组的作用!说的太繁琐怕你听不懂,用一句话来说明:返回失配位之前的最长公共前后缀!


不管你懂不懂这句话,下面的文字和图应该会让你懂这句话的意思以及作用的!
首先,我们取之前已经匹配的部分(即蓝色的那部分!):

我们在上面说到next数组的作用时,说到“最长公共前后缀”,体现到图中就是这个样子:

接下来,就是最重要的了:

没错,这个就是next数组的作用了: 返回当前的最长公共前后缀长度,假设为len。
因为数组是由0开始的,所以next数组让第len位与主串匹配就是拿最长前缀之后的第1位与失配位重新匹配,避免匹配串从头开始!如下图所示:

(即重新匹配刚才的失配位)

接下来最重要的,也是KMP算法的核心所在:就是next数组的求解!
不过,在这里我找到了一个全新的理解方法!如果你懂的上面我写的的,那么下面的内容你只需稍微思考一下就行了!

跟刚才一样,我用一句话来阐述一下next数组的求解方法,其实也就是两个字:

继承

a、当前面字符的前一个字符的对称程度为0的时候,只要将当前字符与子串第一个字符进行比较。这个很好理解啊,前面都是0,说明都不对称了,如果多加了一个字符,要对称的话最多是当前的和第一个对称。比如agcta这个里面t的是0,那么后面的a的对称程度只需要看它是不是等于第一个字符a了。

b、按照这个推理,我们就可以总结一个规律,不仅前面是0呀,如果前面一个字符的next值是1,那么我们就把当前字符与子串第二个字符进行比较,因为前面的是1,说明前面的字符已经和第一个相等了,如果这个又与第二个相等了,说明对称程度就是2了。有两个字符对称了。比如上面agctag,倒数第二个a的next是1,说明它和第一个a对称了,接着我们就把最后一个g与第二个g比较,又相等,自然对称成都就累加了,就是2了。 

c、按照上面的推理,如果一直相等,就一直累加,可以一直推啊,推到这里应该一点难度都没有吧,如果你觉得有难度说明我写的太失败了。

当然不可能会那么顺利让我们一直对称下去,如果遇到下一个不相等了,那么说明不能继承前面的对称性了,这种情况只能说明没有那么多对称了,但是不能说明一点对称性都没有,所以遇到这种情况就要重新来考虑,这个也是难点所在。

如果蓝色的部分相同,则当前next数组的值为上一个next的值加一,如果不相同,就是我们下面要说的!

如果不相同,用一句话来说,就是:

从前面来找子前后缀。

1、如果要存在对称性,那么对称程度肯定比前面这个的对称程度小,所以要找个更小的对称,这个不用解释了吧,如果大那么就继承前面的对称性了。

2、要找更小的对称,必然在对称内部还存在子对称,而且这个必须紧接着在子对称之后。

 

如果看不懂,那么看一下图吧!

好了,我已经把该说的尽可能以最浅显的话和最直接的图展示出来了,如果还是不懂,那我真的没有办法了!

说了这么多,下面是代码实现:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 100
 
void cal_next( char * str, int * next, int len )
{
    int i, j;
 
    next[0] = -1;
    for( i = 1; i < len; i++ )
    {
        j = next[ i - 1 ];
        while( str[ j + 1 ] != str[ i ] && ( j >= 0 ) )
        {
            j = next[ j ];
        }
        if( str[ i ] == str[ j + 1 ] )
        {
            next[ i ] = j + 1;
        }
        else
        {
            next[ i ] = -1;
        }
    }
}
 
int KMP( char * str, int slen, char * ptr, int plen, int * next )
{
    int s_i = 0, p_i = 0;
 
    while( s_i < slen && p_i < plen )
    {
        if( str[ s_i ] == ptr[ p_i ] )
        {
            s_i++;
            p_i++;
        }
        else
        {
            if( p_i == 0 )
            {
                s_i++;
            }
            else
            {
                p_i = next[ p_i - 1 ] + 1;
            }
        }
    }
    return ( p_i == plen ) ? ( s_i - plen ) : -1;
}
 
int main()
{
    char str[ N ] = {0};
    char ptr[ N ] = {0};
    int slen, plen;
    int next[ N ];
 
    while( scanf( "%s%s", str, ptr ) )
    {
        slen = strlen( str );
        plen = strlen( ptr );
        cal_next( ptr, next, plen );
        printf( "%d\n", KMP( str, slen, ptr, plen, next ) );
    }
    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值