【算法】KMP算法超详好懂理解(附全程序模拟过程)

KMP算法详解

当前稿件版本1.0

前言

      今天为了NOIP提高组的模拟准备,早上起来——默写KMP吧!好,提交:TLE;改改改:TLE;我不信!:TLE;58%%RS@^$:WA
     终于在一天的努力后,把KMP给A了。
痛心疾首,决心写一个KMP的详解,不要让其他人掉入这个坑里了

正文

KMP是什么

来源在哪里不用管!只用知道它是一个高效的字符串匹配算法就行了。
AC自动机就是在Trie树上做一个KMP罢了。
故学好KMP还是蛮重要地。

KMP大体是怎么运作的之规定篇

嘿嘿嘿,开始模拟吧!
等等!直接开始模拟会有一些奇怪的问题,不利于理解,毕竟这个博客打的是超详好懂理解的标题,自然要先做出一些规定:
    1. father son均为从0开始的string类型,father son分别为父串和子串
    2. PMT为部分匹配值,为了和son保持一致,也是从0开始。
    3. 变量i为father正在对比的字符下标【模拟中以红色标出】
    4. 变量compared为son正在对比的字符下标【模拟中以黄色标出】
    5. compared也代表了有几个字符匹配成功
好了开始模拟吧

KMP大体是怎么运作的之模拟主程序solve篇

以6do8_8__8q找出8__8q为例
这个是最原始的表格:
origin
add为下标的号码,father、son同规定所示
有一个数组,名为PMT(Partial Match Table),部分匹配值的英文全称的简写。通过PMT_find函数求出其值(这里省略,下文会提及)
PMTfinded
开始对比,初始化compared=0;for(i=0;i<father.size();++i)
inited
此时两个字符不等,compared为0,所以i++,compared保持不动
i+c0
同理直至i=3时
i=3
因为son[compared]==father[i],所以compared++,i++
allp
但是当i=5,compared=2时,又一次不相等
52233
此时按照暴力来说,自然是compared=0,i++但是KMP不一样
它会搞一个大新闻,将compared=PMT[compared-1]等等,是0?额…是这个样例的问题,不用理!(注释:这一段的操作用文字叙述就是:用已匹配的字符数-其对应的部分匹配值)
233
例如在father="BBBBA",son="BBA"时,i=2,compared=2时会这样子:
after233
compare=PMT[compared-1],即为1,因为循环的因素i依然++,于是就成为了这样子
2222233333
看出PMT可以减去一些枚举。
接下来就是各种compared++与i++了,最后compared==son.size(),结束循环
这里写图片描述
注意:此时son[compared]越界了,所以注意程序处理顺序

KMP大体是怎么运作的之模拟部分匹配值PMT_find篇

部分匹配值的官方解释是:”前缀”和”后缀”的最长的共有元素的长度。
“部分匹配”的实质是,有些字符串头部和尾部会有重复。比如,”ABCDAB”之中有两个”AB”,那么它的”部分匹配值”就是2(”AB”的长度)。搜索词(即i、compared移动)移动的时候,第一个”AB”向后移动4位(字符串长度-部分匹配值),就可以来到第二个”AB”的位置。
那么怎么求呢,就是根据上一位以及上一位的字串来进行判断求max(因为字串的部分匹配值总是比母串大,所以直接搜就可以了)
- 貌似还是有点难懂啊,就拿ABABAC说吧,假设正在第3个A上做判断,前面有了个ABA,如果这里(即以第三个A为最后一个字符)有一个ABA——岂不美哉?
- 假设正在C上做判断,前面有一个ABAB,如果这里有一个ABAC不好么?然而没有…好的,那么此时是不是搜第二个B的BA?不是的,ABABA的最长出现字串是AB(BA也可以,但是C后面是A,A的视角中是AB),因此AB==AC?不,这么一直搜下去,没有……那就是0呗!
不懂的朋友们,拿起纸和笔,来手动按照下面的源码来一发模拟吧!自己动手才好理解

代码

/*build 2017.7.22*/
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

const int maxlenth=100001;

bool flag=0;
int want;
int PMT[maxlenth];
string father;
string son;

void PMT_find()
{
    int comparing;
    PMT[0]=0;
    for(int i=1;i<want;++i)
    {
        comparing=PMT[i-1];
        while(son[comparing]!=son[i])
        {
            if(comparing==PMT[comparing]) break;
            comparing=PMT[comparing];
        }
        if(son[comparing]==son[i]) ++comparing;
        PMT[i]=comparing;
    }
}

void solve()
{
    int compared=0;
    for(int i=0;i<father.size();++i)
    {
        /*if(father[i]!=son[compared])
        {
            i=i+(compared-PMT[compared]);
            compared=PMT[compared];
        }
        if(father[i]==son[compared]) ++compared;*/
        //优化
        while(compared and father[i]!=son[compared])
        {
            compared=PMT[compared-1];
        }
        if(father[i]==son[compared]) ++compared;

        if(compared==want)
        {
            flag=1;
            cout<<i+1-want+1<<endl;
            compared=PMT[compared-1];
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin>>father>>son;
    want=son.size();
    PMT_find();
    /*
    for(int i=0;i<son.size();++i)
    {
        cout<<PMT[i]<<" ";
    }

    cout<<endl;
    */

    solve();
    if(flag==0)
    {
        cout<<"no"<<endl;
    }
    return 0;
}
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值