KMP算法详细讲解,next数组构造详解
来源:互联网 发布:js文件如何缩小 编辑:程序博客网 时间:2024/05/18 00:49
日常更新。今天我们讲的是KMP算法,先来看道题目:
题目描述
如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置。
输入输出格式
输入格式:
第一行为一个字符串,即为s1(仅包含大写字母)
第二行为一个字符串,即为s2(仅包含大写字母)
输出格式:
若干行,每行包含一个整数,表示s2在s1中出现的位置
输入输出样例
输入样例#1:
ABABABC
ABA
输出样例#1:
1
说明
时空限制:1000ms,128M
数据规模:
设s1长度为N,s2长度为M
对于30%的数据:N<=15,M<=5
对于70%的数据:N<=10000,M<=100
对于100%的数据:N<=1000000,M<=1000
首先拿到这道题目,不知道大家有没有头绪,一般很容易想到的是朴素算法
如下所示:
cin>>str1;cin>>str2;int len1=strlen(str1);int len2=strlen(str2);int j=0;for(int i=0;i<len1;i++){if(str1[i]==str2[j]){j++;if(j==len2){cout<<i-j+2<<endl;j=0;}}else{j=0;}}
此算法的效率是O(M*N),效率非常的低下,不断地做重复做的事情。
因此有了有了KMP(又叫看毛片算法),KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串匹配次数以达到快速匹配的目的。具体实现就是实现一个next数组,时间复杂度O(M+N)。
这种算法不太容易理解,最关键的是构造next数组,网上有很多方法,大多讲的晦涩难懂。今天,我用自己的语言最简单的理解方法讲解一下next数组的构造,如果next数组懂了,kmp算法基本就能懂了。
首先,我们以模式串pattern[] = { "ababaaababaa"}为例,next数组如下图所示:
我们先让pattern[1]=0,pattern[2]=1;至于pattern[0]可以赋值为-1,也有人用它存pattern的长度。
我们从i=3开始,判断pattern[i-1]==pattern[next[j]]是否相等;如果相等,则next[i]=next[j]+1;若不等,则j=next[j],返回继续找,直到相等,就next[i]=next[j]+1,若没有相等,切已经返回到下标j=1;就直接next[i]=1。
下面,我们来具体演示一下上图表的构造过程:
当i=3时,j=i-1
pattern[i-1]=pattern[2]=b;
pattern[next[j]]=pattern[1]=a;
因为b!=a且j=2>1
所以j=next[j]=next[2]=1;
此时已经来到了第一个元素位置,所以直接置1,next[i]=1,next[3]=1
当i=4时,j=i-1
pattern[i-1]=pattern[3]=a
pattern[next[j]]=pattern[1]=a
因为a=a切j=3>1
所以next[i]=next[j]+1
=next[3]+1
=1+1=2
next[4]=2
当i=5时,j=i-1
pattern[i-1]=pattern[4]=b
pattern[next[j]]=pattern[2]=b
因为b=b切j=4>1
所以next[i]=next[j]+1
=next[4]+1
=2+1
next[5]=3
当i=6时,j=i-1
pattern[i-1]=pattern[5]=a
pattern[next[j]]=pattern[3]=a
因为a=a切j=5>1
所以next[i]=next[j]+1
=next[5]+1
next[6]=3+1=4
当i=7时,j=i-1
pattern[i-1]=pattern[6]=a
pattern[next[j]]=pattern[4]=b
因为a!=b切j=6>1 所以 j=next[j]=next[6]=4
pattern[next[j]]=pattern[next[4]]=pattern[2]=b
因为a!=b切j=4>1 所以 j=next[j]=next[4]=2
pattern[next[j]]=pattern[1]=a
因为a=a切j=2>1
所以next[i]=next[j]+1
=next[2]+1
next[7]=1+1=2
当i=8时,j=i-1
pattern[i-1]=pattern[7]=a
pattern[next[j]]=pattern[2]=b
因为a!=b且j=7>1
所以j=next[j]=next[7]
j=2
pattern[next[j]]=pattern[1]=b
因为b=b切j=2>1
所以next[i]=next[j]+1
=next[2]+1
=2
当i=9时,j=i-1
pattern[i-1]=pattern[8]=b
pattern[next[j]]=pattern[2]=b
因为b=b且j=8>1
所以next[i]=next[j]+1
=next[8]+1
next[9]=3
当i=10时,j=i-1
pattern[i-1]=pattern[9]=a
pattern[next[j]]=pattern[3]=a
因为a=a切j=9>1
所以next[i]=next[j]+1
=next[9]+1
next[10]=3+1=4
当i=11时,j=i-1
pattern[i-1]=pattern[10]=b
pattern[next[j]]=pattern[4]=b
因为b=b且j=10>1
所以next[i]=next[j]+1
=next[10]+1
next[11]=5
当i=12时,j=i-1
pattern[i-1]=pattern[11]=a
pattern[next[j]]=pattern[5]=a
因为a=a且j=11>1
所以next[i]=next[j]+1
=next[11]+1
next[12]=6
下面我们由上面的推理过程用代码实现,我们通过一步一步的演算,不难发现当pattern[i-1]!=pattern[j] && j >1 的时候就要做j=next[j],因此,我们可以用循环代替
while(s[i-1]!=s[next[j]] && j>1)j=next[j];
我们又可以发现,当上述不成立的时候,就要做next[i]=next[j]+1,用代码又可以这又代替
if(j>1 && s[i-1]==s[next[j]])next[i]=next[j]+1;
这样写还有点问题,就是当j=1的时候,需要进行next[i]=1;即:
if(j>1 && s[i-1]==s[next[j]])next[i]=next[j]+1;elsenext[i]=1;
我们在套个外循环,因为子串pattern要循环,再加上当初pattern1和2赋值,如下:
pattern[1]=0;pattern[2]=1;for(int i=3;i<=strlen(pattern);i++){int j=i-1;while(s[i-1]!=s[next[j]] && j>1)j=next[j];if(j>1 && s[i-1]==s[next[j]])next[i]=next[j]+1;elsenext[i]=1;}
这样next数组就构造完成了!
结果如下所示:
我们再找一个pattern试试看:
由此来看,next数组构造完成了。
next构造好了,下面就简单了。我们只需要和主串T进行匹配就行了。
定义一个i=1,j=0;如果T[i]!=pattern[j+1] && j>0 也就是当模式串和主串的第一个不相等的时候,就把j=next[j],进行最大匹配移动,依次类推;相反,如果相等,就做++j,让j指向pattern下一个元素和T下一个进行比较;直到j=strlen(pattern)时,就输出i的位置cout<<i-strlen(pattern)+1,如果i==strlen(T)的时候,则就未找到,输出“-1”.
代码如下:for(int i=1,j=0;i<=strlen(T);i++){while(T[i]!=pattern[j+1] && j>0)j=next[j];if(T[i]==pattern[j+1])++j;if(j==strlen(pattern)){cout<<i- strlen(pattern)+1<<endl;//j= strlen(pattern);break;}if(i== strlen(T)){cout<<"-1"<<endl;}}
到此为止,kmp算法的核心就讲完了。我想只要你理解了我所讲的,kmp算法应该就能掌握了,再找几道例题刷刷,巩固巩固,基本就将kmp算法收入囊中了。
下面附上C++的完整代码:
#include"iostream"#include"string.h"using namespace std;void fun(char s[],int next[],int len){next[1]=0;next[2]=1;for(int i=3;i<=len;i++) {int j=i-1;while(s[i-1]!=s[next[j]] && j>1)j=next[j];if(j>1 && s[i-1]==s[next[j]])next[i]=next[j]+1;elsenext[i]=1;}}void kmp(char p1[],char p2[],int next[],int len){for(int i=1,j=0;i<=len;i++){while(p1[i]!=p2[j+1] && j>0)j=next[j];if(p1[i]==p2[j+1])++j;if(j==next[0]){cout<<i-next[0]+1<<endl;j=next[0];break;}if(i==len){cout<<"-1"<<endl;}}}int main(){char p[100];char s[100];int next[100];cin>>p+1;int lenp=strlen(p+1);cin>>s+1;int len=strlen(s+1);next[0]=len;fun(s,next,len);//kmp(p,s,next,lenp);for(int q=1;q<=len;q++) cout<<next[q]<<" ";return 0;}
代码中注释掉的break可以注释掉,注释掉就变成了全查找,把主串中的所有子串中的全部查找出来并输出位置,加个break找到第一个子串位置输出,就结束了。具体可根据需求来。
- KMP算法详细讲解,next数组构造详解
- KMP算法next数组详解
- KMP算法next数组详解
- kmp算法的next数组讲解
- KMP算法(next 数组讲解)
- KMP next数组讲解
- KMP算法和KMP算法中next数组的讲解
- 【详解KMP算法 next数组详解】
- KMP算法的Next数组详解
- KMP算法-之next数组-详解
- KMP算法的Next数组详解
- 转载 KMP算法中next数组详解
- KMP算法的Next数组详解
- KMP算法的Next数组详解
- KMP算法及next数组详解
- KMP算法的Next数组详解
- KMP算法的Next数组详解
- KMP 算法 next数组
- 执行计划中三种最常用连接方式的伪码实现
- opencv录制视频
- java new子类对象过程
- tomcat路径-将webapp切换到root下
- A*算法 hdu1043 Eight 人工智能算法, 还有康拓展开得hash值
- KMP算法详细讲解,next数组构造详解
- The type or namespace name `MovieTexture' could not be found. Are you missing an assembly reference?
- 巴什博奕
- Java StringBuffer与StringBuider
- 数据结构之链表(二)
- Python中使用SAX解析XML及实例
- Angular Hello World,Angular 简单DEMO
- 很特别的一个动态规划入门教程
- hdu6103