初探KMP算法
算法简述
KMP是单模匹配算法,在一个长度为n的文本串中查找一个长度为m的模式串。它的时间复杂度是O(m+n),是这类算法的理论最优复杂度。
模式匹配:在长为n的字符串S中,找到某个长为m的模式串P,复杂度至少是O(m+n)。
朴素的模式匹配算法
普通的暴力方法是从S中逐个字符开始匹配P中每个字符。最好复杂度是O(m+n),例如S = "abcdefg123", P = "123"
,只需处理7+3=10次。最差复杂度是O(mn),例如S = "aaaaaaab" ,P = "aab"
,要匹配5*3+3=18次。
KMP算法
KMP是在任何时候都能达到O(m+n)复杂度的算法。它通过预处理P,使得在与S匹配时能够跳过一些字符,达到快速匹配的目的。
算法原理
KMP中,在比较S[i],P[j]时,i指针会走到S串尾不回溯,而只回溯j指针达到快速匹配目的,其是通过KMP的核心next[]数组做到,回溯时next[]数组会给出j回溯的位置。
next数组
next[i+1] = P[i]之前(包括i)相同前后缀的最大长度
,从而做到在S[i+1]!=P[j]
后回溯时,通过j = next[j]
就可以回到P[j-1]之前(包括j-1)相同前后缀的前缀末端+1的位置开始匹配。
我们可以通过如下代码得到next[]数组,复杂度为O(m):
void getNext(char *p,int plen){
Next[0] = -1;
for(int i = 0;i<plen-1;i++){
int j = Next[i];
while(~j && p[i] != p[j]) j = Next[j];
Next[i+1] = ~j?j+1:0;
}
}
//next[i+1] = p[i]之前(包括i)的公共前后缀最大长度,j代表前缀末端
//每次在p[i]匹配前后缀时,若匹配则在next[i+1]存放前缀长度j+1(即从这个位置可回溯并匹配的最近下标),不匹配则回溯到next[j](最近的公共前后缀串下标+1)
用next[]数组进行匹配
得到next数组后,我们只需要在S[i],P[j]匹配失败时将指针j回溯到next[j]的位置,继续匹配即可。
实现如下,复杂度为O(n):
int kmp(char *s,char *p){
int cnt = 0;
int slen = strlen(s);
int plen = strlen(p);
getNext(p,plen);
int j = 0;
for(int i = 0;i<slen;i++){
while(~j && s[i] != p[j]) j = Next[j];
j++;
if(j == plen){
ans[cnt++] = i - plen + 1;
i--;
j = Next[j-1];
}
}
return cnt;
}
//求next数组
//匹配字符串s,p.在s[i]处不匹配,则j=next[j]回溯到最近的公共前后缀串下标+1处(即用s已匹配部分的后缀去找p的已匹配部分的前缀相同的最大长度并从后继续匹配)
//在s[i]匹配,则i+1,j+1继续匹配
//匹配完一个,j=next[j]回溯到最近的公共前后缀串下标+1处,继续匹配
完整代码
#include <bits/stdc++.h>
#define TLE std::ios::sync_with_stdio(0),cout.tie(0),cin.tie(0)
using namespace std;
char s[1010],p[1010];
int Next[1010];
int ans[1010];
void getNext(char *p,int plen){
Next[0] = -1;
for(int i = 0;i<plen-1;i++){
int j = Next[i];
while(~j && p[i] != p[j]) j = Next[j];
Next[i+1] = ~j?j+1:0;
}
cout<<"Next:";
for(int i = 0;i<plen;i++){
cout<<Next[i]<<' ';
}
}
//next[i+1] = p[i]之前(包括i)的公共前后缀最大长度,j代表前缀末端
//每次在p[i]匹配前后缀时,若匹配则在next[i+1]存放前缀长度j+1(即从这个位置可回溯并匹配的最近下标),不匹配则回溯到next[j](最近的公共前后缀串下标+1)
int kmp(char *s,char *p){
int cnt = 0;
int slen = strlen(s);
int plen = strlen(p);
getNext(p,plen);
int j = 0;
for(int i = 0;i<slen;i++){
while(~j && s[i] != p[j]) j = Next[j];
j++;
if(j == plen){
ans[cnt++] = i - plen + 1;
i--;
j = Next[j-1];
}
}
return cnt;
}
//求next数组
//匹配字符串s,p.在s[i]处不匹配,则j=next[j]回溯到最近的公共前后缀串下标+1处(即用s已匹配部分的后缀去找p的已匹配部分的前缀相同的最大长度并从后继续匹配)
//在s[i]匹配,则i+1,j+1继续匹配
//匹配完一个,j=next[j]回溯到最近的公共前后缀串下标+1处,继续匹配
int main(){
TLE;
cin>>s>>p;
int cnt = kmp(s,p);
cout<<endl<<"Match:"<<cnt<<endl;
cout<<"Location:";
for(int i = 0;i<cnt;i++){
cout<<ans[i]<<' ';
}
return 0;
}
本文来自博客园,作者:空白菌,转载请注明原文链接:https://www.cnblogs.com/BlankYang/p/15832116.html