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为例
这个是最原始的表格:
add为下标的号码,father、son同规定所示
有一个数组,名为PMT(Partial Match Table),部分匹配值的英文全称的简写。通过PMT_find
函数求出其值(这里省略,下文会提及)
开始对比,初始化compared=0;for(i=0;i<father.size();++i)
此时两个字符不等,compared为0,所以i++,compared保持不动
同理直至i=3时
因为son[compared]==father[i],所以compared++,i++
但是当i=5,compared=2时,又一次不相等
此时按照暴力来说,自然是compared=0,i++但是KMP不一样
它会搞一个大新闻,将compared=PMT[compared-1]等等,是0?额…是这个样例的问题,不用理!(注释:这一段的操作用文字叙述就是:用已匹配的字符数-其对应的部分匹配值)
例如在father="BBBBA",son="BBA"时,i=2,compared=2时会这样子:
compare=PMT[compared-1],即为1,因为循环的因素i依然++,于是就成为了这样子
看出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;
}