在讲解kmp算法之前,先讨论下一般的模式匹配算法
先看下代码
int index(string s,string t,int pos)
{
for (int i=pos;i<s.length()&&i+t.length()<=s.length();i++){
int flag=1;
for (int j=0;j<t.length();j++){
if (s[i+j]!=t[j]){
flag=0;
break;
}
}
if (flag){
return i+1;
}
}
return 0;
}
返回的是b串第一次在a串的pos位置后出现的位置
可以显而意见的是复杂度为O(n*m)
在a串和b串为如下的形式的时候时
a:0000000001
b:001
你会发现b串在a串中进行匹配的时候,要到最后一次才会匹配成功,而前面的匹配很多都做了无用功,i指针根本就没必要回朔
kmp算法
kmp算法巧妙的解决了这个问题
将问题的复杂度下降到O(n+m)
那么它是怎么实现的呢?
先给大家看几个图示
当a串与b串进行比较的时候,可能在分别在i和j的位置的时候匹配失败了
这个时候如果是一般匹配方法就会i指针回朔到b1+1位置,而j则会回朔到b2位置
但是如果仔细观察下你会发现(b1,i-1)和(b2,j-1)是完全匹配的,而且你也可能会发现可能在b串中本身就存在(b2,s)完全等于(k,j-1)的情况,那么这个时候何不直接把j回朔到s+1的位置呢?
如下图所示
这样算法复杂度就可以得到很好的优化
但是如何实现以上过程呢
这里引进next数组
next[i]:=如果匹配失败了j指针应该滑向的位置
也可以理解成最大的一个值使得对于b串而言(0,next[i]-1)完全等于(j-next[i],j-1)
则可以定义如下
next[i]=-1,i==0
next[i]=k,,0<k<j,其中"p0p1p2p3...pk-1"="pj-kpj-k+1pj-k+2...pj-1"
next[i]=0,其余的情况,其实也就是b串0到j-1的位置上没有前后相互匹配的情况
那么只要求出next数组的值,就万事大吉了
下面给出代码
void getNext(char *p,int *next)
{
int j,k;
next[0]=-1;
j=0;
k=-1;
while(j<strlen(p)-1)
{
if(k==-1||p[j]==p[k]) //匹配的情况下,p[j]==p[k]
{
j++;
k++;
next[j]=k;
}
else //p[j]!=p[k]
k=next[k];
}
}
int KMPMatch(char *s,char *p) //如果不存在匹配项,则返回-1,否在返回开始的匹配位置
{ //p匹配到s,s必须比p长
int next[100];
int i,j;
i=0;
j=0;
getNext(p,next);
while(i<strlen(s))
{
if(j==-1||s[i]==p[j])
{
i++;
j++;
}
else
{
j=next[j]; //消除了指针i的回溯
}
if(j==strlen(p))
return i-strlen(p);
}
return -1;
}
写法二,思路都是一样的,只是代码习惯的不同。。
#include <bits/stdc++.h>
using namespace std;
#define maxn 105
int next[maxn];
void get_next(string b)
{
next[0]=-1;
int j=0;
for (int i=1;i<b.length();i++){
while(j>0&&b[i]!=b[j]) j=next[j];
if (b[i]==b[j]) next[i]=j++;
}
}
int kmp(string a,string b,int pos)
{
memset(next,0,sizeof(next));
get_next(b);
int j=0;
for (int i=pos;i<a.length();i++){
while(j>0&&a[i]!=b[j]) j=next[j];
if (a[i]==b[j]) j++;
if (j==b.length()){
return i-b.length()+1;
}
}
return -1;
}
int main()
{
string a,b;
while(cin>>a>>b){
printf("%d\n",kmp(a,b,0));
}
return 0;
}