kmp算法讲解

kmp算法本身,解决的是判断模板字符串T,是否是字符串s的子串的问题。

当只需判断子串首次出现的位置,或是否包含子串。可以用库函数strstr(s,t )代替,判断t是否为s的子串来代替。该函数的返回值是t首次出现的位置,如果t不是s的子串则返回NULL。该函数的复杂度与kmp类似。

kmp算法主要分为两个部分第一部分是求解nexta数组(注意此处不能写next因为肯能会和C++的库变量重名,导致编译错误),第二部分利用nexta数组进行匹配。

先说为什么要求nexta数组。朴素的匹配方法。当发现字符串不匹配时,就要重头开始重新匹配,造成很高的复杂度(m*n)。如下面三组图片所示(图一图二图三图四,来自http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html

图一

图二

图三

kmp算法对模式串进行处理,得出当匹配到不相等的地方时,指向模式串的指针重新指向的位置(朴素法的指针指向的位置是0)。且指向母串的指针始终是不会向左移动的,及只向右移动。当发现不匹配时利用nexta数组产生的效果。如图四所示。只有j改变了i不动,再次判断。使得复杂度是线性的。

图四

那么nexta数组的含义是什么呢?

设进行处理的字符串为s。nexta[i] = k.k是使得s[0...k-1] == s[i - k,i - 1]成立的最大值。

也就是求s的前缀,和以i-1结尾的后缀的最大值。

这种后缀等于前缀的模式保证移动是合适的

那么如何求解nexta数组呢?

此处借鉴“北京小王子”的方法

入口:点击打开链接

http://www.cnblogs.com/tangzhengyue/p/4315393.html

 下面我们看一个彩图:


下面我们用数学归纳法来解决这个填值的问题。
这里我们借鉴数学归纳法的三个步骤:
1、初始状态
2、假设第j位以及第j位之前的我们都填完了
3、推论第j+1位该怎么填

初始状态我们稍后再说,我们这里直接假设第j位以及第j位之前的我们都填完了。也就是说,从上图来看,我们有如下已知条件:
next[j] == k;
next[k] == 绿色色块所在的索引;
next[绿色色块所在的索引] == 黄色色块所在的索引;
这里要做一个说明:图上的色块大小是一样的(没骗我?好吧,请忽略色块大小,色块只是代表数组中的一位)。

我们来看下面一个图,可以得到更多的信息:

1.由"next[j] == k;"这个条件,我们可以得到A1子串 == A2子串(根据next数组的定义,前后缀那个)。

2.由"next[k] == 绿色色块所在的索引;"这个条件,我们可以得到B1子串 == B2子串

3.由"next[绿色色块所在的索引] == 黄色色块所在的索引;"这个条件,我们可以得到C1子串 == C2子串

4.由1和2(A1 == A2,B1 == B2)可以得到B1 == B2 == B3

5.由2和3(B1 == B2, C1 == C2)可以得到C1 == C2 == C3

6.B2 == B3可以得到C3 == C4 == C1 == C2

上面这个就是很简单的几何数学,仔细看看都能看懂的。我这里用相同颜色的线段表示完全相同的子数组,方便观察。

 

接下来,我们开始用上面得到的条件来推导如果第j+1位失配时,我们应该填写next[j+1]为多少?

next[j+1]即是找str从0到j这个子串的最大前后缀:

我们已知A1 == A2,那么A1和A2分别往后增加一个字符后是否还相等呢?我们得分情况讨论:

(1)如果str[k] == str[j],很明显,我们的next[j+1]就直接等于k+1。

  用代码来写就是next[++j] = ++k;

(2)如果str[k] != str[j],那么我们只能从已知的,除了A1,A2之外,最长的B1,B3这个前后缀来做文章了。(为什么B1,B3是最长的?假设它们不是最长的,还有更长的next[k]就等于更长的了)

那么B1和B3分别往后增加一个字符后是否还相等呢?

由于next[k] == 绿色色块所在的索引,我们先让k = next[k],把k挪到绿色色块的位置。如果还不相等就一直往前挪。挪到开头会做特殊处理。

 

由于j+1位之前的next数组我们都是假设已经求出来了的,因此,上面这个递归总会结束,从而得到next[j+1]的值。

 

我们唯一欠缺的就是初始条件了:

next[0] = -1,  k = -1, j = 0

另外有个特殊情况是k为-1时,不能继续递归了,此时next[j+1]应该等于0,即把j回退到首位。

即 next[j+1] = 0; 也可以写成next[++j] = ++k

获得nexta的代码如下

//定义nexta[]大小与s相同
void getnexta(char s[])
{
    memset(nexta,0,sizeof(nexta));
    int n = strlen(s);
    int k = -1,j = 0;
    nexta[0] = -1;
    while(j < n )
    {

        if(k == -1 || s[k] == s[j])
        {
            nexta[j + 1] = k + 1;
            j ++;
            k ++;
        }
        else
        {
            k = nexta[k];
        }
    }

}
下面谈谈 第二部分利用nexta数组进行匹配。

同样设两个指针i,j初始值都为零。假设j为模式串t的指针,i为母串s的指针。当s[i] ==t[j]时,显然i ++,j++.当j= -1时j要重新等于零,i也要向右移一位(因为这个位置已经与模式串全部试过了)除了这两种情况之外,剩余的状况及两个字符串不匹配且并未挪到开头之前,则j = nexta[j].

以下是kmp代码


int kmp(char s[],char t[])//t模式串,s母串.此种为返回首次匹配的位置,不能匹配则返回-1.
{
    getnexta(t);
    int n = strlen(s),m = strlen(t);
    int i = 0,j = 0;
    while(i < n && j < m)
    {
        if(j == -1 || s[i] == t[j])
        {
            i ++;
            j ++;
        }
        else
        {
            j = nexta[j];
        }
        if(j == m)//根据题目要求改变
        {
            return i - j+ 1;
        }
    }
    return -1;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值