KMP算法

KMP算法探析

笔者在龙书的练习中遇到了Trie树,又遇到KMP,为了看懂KMP不得不四处寻找资料,翻阅别人的博客,但是最后都没有很好的理解KMP,不得已在科学上网后找到了一篇老外的博客,看完之后才理解了KMP,这里记录了笔者自己的理解,因为本人水平有限,可能很难清楚地描述KMP。

KMP算法的高效率是因为它消除了字符串匹配过程中发生失配时的回溯。

这里,假设读者已经了解过暴利匹配算法,这里笔者不多做解释了,着重说重点。读者可以带着下面的问题阅读此文,笔者会按照顺序讲解。

  • 为什么需要回溯?
  • 具体什么情况下需要回溯?
  • 怎么识别需要回溯的情况?
  • 怎么消除回溯?

首先我们来说暴力匹配算法遇到失配时候的处理

源字符串ABCDEFFF
匹配字符串ABCFED

这里用i和j分别表示当前匹配到的位置,起始为0。

直接来看,第一次字符串匹配失败将在i=3,j=3的时候,这时候暴力匹配算法会将i置为1,j置为0,这个过程就是我们所说的回溯,但是这里我们可以直观的看出来,此次回溯并没有任何价值,因为在源字符1~3的位置并没有字母A,所以就算是回溯了也不过是不断匹配失败,不断进行回溯而已。所以,我们为什么需要回溯?,请看下面这个例子。

源字符串AAAB
匹配字符串AAB

还是用i和j分别表示当前匹配到的位置,起始为0。

这次匹配失败将发生在i和j分别等于2的时候,而这时候我们回溯,i=1,j=0,然后在进行匹配,则可以匹配成功。所以,回溯还是有必要的。

现在来看,回溯需要分情况,那么到底什么情况下需要进行回溯呢?
答:已匹配的那部分字符串中存在相等的真前缀字符串和真后缀字符串。

关于真前缀和真后缀我直接摘抄自龙书

串S的前缀(prefix)是从s的尾部删除0个或多个字符后得到的串。
串S的后缀(suffix)是从s的开始处删除0个或多个符号后得到的串。
串S的真前缀,真后缀是既不等于空字符串,也不等于S本身的串。

为了方便讲解,我们将相等的真前缀字符串和真后缀字符串称为“公共真前后缀”,一个串中最长的公共真前后缀我们称之为“公共最大真前后缀”。再回到刚才的问题,那么首先,我们说已匹配的那部分字符串中存在相等的真前缀和真后缀的时候我们才需要回溯(最终我们并不需要进行回溯),拿一个例子来看。

源字符串ABABABCD
匹配字符串ABABC

如果拿这两个字符串进行匹配,那么失配会发生在j=4,i=4的时候,这里已完成匹配的字符串部分是 ABAB

StrABAB
Index0123

而这部分字符存在相同的真前缀和真后缀分别是AB,AB,也就是索引(0,1),(2,3)这两个位置的字符串。那么,我们回溯到哪呢?

还是拿刚才的例子

源字符串ABABABCD
匹配字符串ABABC

直接来看,我们将i回溯到2的位置,j回溯到0的位置便可成功的完成匹配。
但是,这里我们还有一种其他的方法完成与回溯相同的目的,也就是消除回溯的方法,那就是只调整j来完成匹配,具体怎么做呢?,我们保持i不变,将j变成”公共最大真前后缀的长度,相当于真前缀后面第一个字符的位置”,这时候i=4,j=2最终依然能完成匹配。
接下来我们阐述一下KMP算法是如何做的,KMP算法的时间复杂度为O(M+N),M为源字符串长度,N为匹配字符串长度。首先KMP根据匹配字符串生成一个 失配数组,失配数组保存着匹配字符串中各个前缀的公共最大真前后缀的长度,这样在失配的时候,只需要根据已匹配的那部分的字符串长度(也就是匹配字符串前缀的长度)获取到公共真前后缀的长度,然后在将j置为那个长度值。

代码

int m_kmp(char *ori, char* patter)
{
    int plen = strlen(patter);
    int i, j, mismatch[plen + 1];
    j = -1;
    i = 0;
    mismatch[i] = j;
    while(i < plen)
    {
        while(j >= 0 && patter[i] != patter[j]) j = mismatch[j];
        i++;
        j++;
        mismatch[i] = j;
    }

    i = 0;
    char* o = ori;
    while(*o)
    {
        printf("i %d \n", i);
        if(*o == patter[i])
        {
            o++;
            i++;
            if(!patter[i])
                return (o - ori) - plen;
        }else
        {
            i = mismatch[i];
        }
    }
    return -1;
}

如果你能认真阅读到这里,那么我真的是非常感谢!!
推荐阅读:July大神关于KMP的解释,很详细。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值