全部数据结构、算法及应用课内模板请点击:https://blog.csdn.net/weixin_44077863/article/details/101691360
将KMP算法,就要先讲讲它是用来干什么的,为什么叫这个名字,暴力的算法又是怎样的
KMP是三人Knuth,Morris,Pratt,他们仨发明了这个算法,所以叫KMP。。。(不要惊讶。。)
然后它是用来解决字符串的模式匹配问题(大白话就是找子串)
举个例子说就是,给你一个主串 S ,模式串 T,问你S里含有多少个子串T,子串开头下标多少
比如 S=aaaaa T=aa,于是有4个子串,下标分别是0,1,2,3
再比如
有1个子串,下标5
然后说一说暴力的算法,也就是你没学过KMP保证就那样去写了
叫做BF算法,暴风(Brute Force)算法(就是暴力啦~),是普通的模式匹配算法
或者也可以叫做 NaiveStrMatching 朴素的模式匹配算法
暴力的方法事实上就是很单纯的二重循环,S下标0开始,然后每次T下标0开始去和S比较
时间复杂度O(nm)
然后人们意识到,T每次向后挪一位很没必要,然后从下标0开始一位一位地比较也完全没必要
比如还是上面那个ST
第一轮比较结果:
用人眼看的话,会觉得下一次比较有价值的比较是:
直接比较图中标记的p处
Naive的暴力算法接下来会比较:
并且挪到2号位的时候,T也会从0开始比较
这时候K、M、P三个人就开始研究人眼的匹配特性了
第一轮比较中,T匹配上的前缀串是 : ABAB
这一串有 真前缀串:A、AB、ABA
真后缀串:B、AB、BAB
他们发现,上述加粗的部分相同
于是规律性的东西出现了,我们再来看一下第一轮比较的图
对于T T01=T23,然后看后缀23,S23=T23=T01
于是将 T01挪至S23,并直接比较S4和T2即可
所以,KMP算法原理总结成一句话为:
比较失败时,看已匹配成功的前缀串A,取A的一个真后缀串Y,有一个A的真前缀串X与之相等
且此时lenX(=lenY)长度尽可能长,将X挪至Y的位置,直接比较X的下一个字符和失败位置的字符
不断循环以上操作,直到无法继续匹配
事实上,这个算法还远远没有说完
每次都现找后缀和与之对应的前缀,时间又是很复杂
于是要先预处理出来对应位置保存起来
比方说 ABAB 的 3号B 对应长度为 2(AB)
我们把这个称之为特征数N
对ABABA有
(教材是N,其实一般写作next会舒服些,但是包含万能头文件的话next会出现多义性报错)
于是这时我们再来看第一轮匹配
i=j=4时,匹配失败,于是移动后 j=N[ j-1 ],接着比较 S[ i ] 与 T[ j ] 即可
当然,如果 j=0(第一位)就已经不相等了就没有N[-1]了,就直接向后移动一位 i 就好
然后预处理特征数N的时候如果太过暴力又会很慢
用动态规划的思想去考虑,预处理这个东西的本质也是一个KMP
比方说4号位的A,N[ 3 ]=2,于是直接 主:T[ 4 ] 和 模式:T[ 2 ]比较
发现 T[4]==T[2],于是 N[4]=N[3]+1=3,接着判断第5位(当然上面这个串没有5,那就结束了)
假设 T[ 4 ] != T[ 2 ],那么就接着判断当前真前缀串的真前缀串的下一位,就是去找判断N[ N[ 3 ] - 1 ]即N[ 1 ]
然后不断重复这样的操作直到相等
当然,假设判断到 0 还不相等,那么 N[ 4 ]就=0
KMP算法模板如下:(复杂度O(n+m))
char s[maxn],t[maxm]; //主串s,模式串t
int n,m,N[maxm],cnt; //主串长n,模式串长m,特征数N,成功匹配个数cnt
vector<int> pos; //成功匹配位置所有下标(第一个字符)
void get_next(){
m=strlen(t);
for(int i=1;i<m;i++){
int j=N[i-1];
while(j&&t[i]!=t[j]) j=N[j-1];
if(t[i]==t[j]) N[i]=j+1;
else N[i]=0;
}
}
void KMP(){
n=strlen(s);
int i=0,j=0;
for(;i<n;i++){
while(s[i]!=t[j]&&j) j=N[j-1];
if(s[i]==t[j]) j++;
if(j==m) cnt++,pos.push_back(i-j+1);
}
}
KMP算法模板题:https://cn.vjudge.net/problem/HihoCoder-1015
可以交一发检验下自己写的KMP对不对