KMP 算法 Kunth-Morris-Pratt

Q:KMP是什么鬼,KMP卖的炸鸡还不错

A:你说的是KFC吧

Q:都一样,反正都是卖炸鸡的,最近买一整只黄金脆皮鸡还送中可

A:那吃好勒您

Q:咕噜咕噜。。。

学习Kunth-Morris-Pratt 算法之前,如果读者还不知道什么是有限自动机的话,那理解KMP算法就比较困难,推荐一遍笔者的有限自动机,当然笔者能力有限。https://blog.csdn.net/giftedpanda/article/details/86774815

Kunth-Morris-Pratt 算法:一种高效的字符串匹配算法,比有限自动机还要高效,有限自动机的时间复杂度为O(n)+O(m∑),而Kunth-Morris-Pratt 算法的时间复杂度为O(n)+O(m)是不是很神奇呢,下面和笔者一起来学习KMP算法吧。

其实KMP算法和有限自动机的算法思想大致是一样的,还是利用的前缀的思想。如上图a所示当偏移为s时匹配上了5个字符,第六个字符是不匹配的,所以我们要考虑下一次偏移,在偏移为s+1时,文本串T的第一个字符不能与模式串P的第一个字符匹配,但是却能与模式串P的第二个字符匹配,所以s+1的偏移时无效的。在偏移为s+2时,有三个字符时匹配的,在一般情况下提前知道这个答案是有用的。

前缀函数:next[q]=max(k,k<p and P(k)为P(q)的后缀)  next[q]是P(q)的真后缀的最长前缀。

KMP算法的精髓就是通过迭代来求解前缀函数。

KMP算法就是会提前计算出模式串P与自己匹配的前缀函数,以便得出下一次有效偏移。为什么要这样做呢。假设从偏移s开始匹配当匹配到第K个字符时,第K+1个匹配失败了,我们可以很有效的利用前面的匹配信息,既然前面能匹配上,证明此时文本串的前K的字符和模式串的前K的字符一样,在进行下一次偏移时,要是我们能知道P的前缀与真后缀的最长匹配长度d,我们知道相对应的字符串是匹配的,我们直接滑到那个位置去匹配就行了,就不用在比较前d个字符了。拿图b来说,当前5个字符"ababa"匹配上时,证明此时文本串也是"ababa",下一次我们就直接从P的前缀与真后缀的最长匹配长度d开始匹配,即偏移为s-d,如图c,模式串的前缀"aba"与文本串的“ababa”的最长匹配长度为3,那么下一次的可能有效偏移就为2。既然是模式串P和文本串T匹配上了,那不就是模式串自己吗,所以我们提前预处理模式串P与自己的前缀与真后缀的最长匹配长度不就行了吗,下一次通过前一次无效偏移的最长匹配长度,就能知道下一次的有效偏移是多少。

如上图就是计算next数组即前缀函数的过程,在无效偏移时通过匹配上的字符的个数,就能得出下一次可能的有效偏移是多少。

笔者认为,这些高效的字符串匹配算法都利用了前缀的思想。即知道自己的前缀和真后缀的最长匹配长度是多少,就把前缀和真后缀对其开始匹配,由于之前是匹配上的,前面部分模式串和文本串都是一样的,那么直接提前预处理模式串P的每一个匹配长度下的P的前缀与真后缀的最长匹配长度,就是一次模式串P要和文本串T要对齐开始匹配的位置。

 

#include<cstdio>
#include<cstring>
using namespace std;
const int maxp=1000+7;
const int maxt=10000+7;
char P[maxp],T[maxt];    //P为模式串   T为模板串
int next[maxp];         //前缀函数
void Compute_Prefix_Function(char* P)      //计算前缀函数的值
{
    int m=strlen(P+1);    //字符串的下标在读取时从1开始的 
    next[1]=0;        //一个字符 不存在真后缀        
    int k=0;          //初始化前缀函数的值为0 即上一次匹配上的长度
    for(int i=2;i<=m;i++){             //迭代求解前缀函数 KMP算法的精髓
        while(k>0&&P[k+1]!=P[i])          
            k=next[k];
        if(P[k+1]==P[i])        //匹配成功
            k=k+1;
        next[i]=k;
    }
    return ;
}
int KMP_Matcher(char* P,char* T)         
{
    int m=strlen(P+1);        //字符串的下标在读取时从1开始的
    int n=strlen(T+1);
    Compute_Prefix_Function(P);
    int q=0;
    for(int i=1;i<=n;i++){
        while(q>0&&P[q+1]!=T[i])          //匹配不成功 寻找更下一个最长前缀
            q=next[q];
        if(P[q+1]==T[i])
            q=q+1;
        if(q==m){
            printf("Pattern occus with shift %d\n",i-m);
        }
    }
}
int main()
{
    while(scanf("%s %s",P+1,T+1)==2){
        KMP_Matcher(P,T);
    }
    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值