1. 朴素匹配算法
过程如图:
翻译成代码:
int index_bf(char *s, char *p)
{
if(s==NULL || p==NULL)
return -1;
int i=0,j=0;
while(s[i+j]!='\0' && p[j]!='\0')
{
if(s[i+j]==p[j])
j++;
else
{
j=0; i++;
}
}
if(p[j]=='\0')
return i;
else
return -1;
}
2. kmp匹配过程
如图:
1. 可以看到匹配过程中 i 跟 j 是同时增加的,不同于朴素匹配算法只增加 j 。 在 i=5, j=5的时候有c!=d,这个时候的处理过程如下图
2. 这里模式串被右移了3个位置,实际上就是j=5变成j=2(
next[5]=2 为什么??)。i=5保持不变,继续匹配直到i: 5->8, j:2->5。上图绿色部分ab两个字符我们没有比较过,因为
next[5]=2隐含了前面2个字符不需要在比较的意思。
3. next[j]=k的含义
1. 直观的含义是::设置j=k, 即把模式串右移j-k个位置。 如:next[5]=2,表示j=2即模式串右移5-2=3个位置。
2. 更进一步的含义是:模式串前面k的字符已经比较过,不需要再比较了,直接从j=k(因为下标从0开始)这个位置开始比较
3. 再进一步的含义可以用下面的图表示(
next[5]=2 ):我们发现了在k=2这个位置,把模式P[0-4]分成了3个部分
"P[0]P[1]"="P[3]P[4]"还有个就是P[2]这3个部分。
举个列子上面的next[5]=2是因为:
4. 用一个比较专业的术语来描述就是,在P[0~j-1]中找一个最长真前缀使得它等于P[0~j-1]的最长真后缀。
这里最长的意思举个例子如下:虽然下图有2种方法把模式串分成3部分,但是我们取k=3
4. 为什么一定是右移j-k
但我们在 j 这个位置发现失配的时候(S[i]!=P[j]), 模式串可以右移1,2,..,j-1。那为什么一定是j-k呢。 下图可以帮助理解
5. 求next
再说下next[j]=k是把P[0]~P[j-1]分成了3个部分:
但是2这个部分是可以为空的:
如果2为空,1跟1相交也是可以的:
注:next[0]= -1, -1 表示S[i]与P[0]不需要比较了,直接比较S[i+1]跟P[0].
按照上面的规则:我们来看几个例子:
求next的算法,虽然我们很容易能看出next的值,但是如果从找最长相等的真前缀跟真后缀这个角度出发的话,求出next数组的复杂度可能比朴素匹配的复杂度还高。实际上的求法是一个迭代的过程。即: next[j] = f(next[j-1]), 因为next[0]=-1已知道,f为某种规则。
算法:
1. 已知next[j]=k, 如果P[j]=P[k], 则next[j+1]=next[j]+1=k+1。原因如下图:
2. 如果P[j]!=P[k]怎么办呢, 只要一直求一个k(k = next[k]), 使得p[k]=p[j], 或者直到k=-1。如图:
上诉过程很容易翻译成代码:
void get_next_tmp(char *p, int next[]){
int l=strlen(p);
int i=0,k=-1;
next[0]=-1;
while(i<l-1)
{
if(k==-1 || p[i]==p[k])
{
i++;k++;
next[i]=k;
}
else
k=next[k];
}
}
6. 改进next
上诉的next还可以再改进,能改进的原因其实也很简单。因为我们再求next[j]的时候只考虑了0~j-1串的情况,至于P[j]到底是什么字符完全没有考虑,实际上 j 这个位置的信息能让我们把模式串更加往右移一些。
1. 先看图吧:
这个图的意思是这样的,首先P[j]!=S[i], 按照上述的规则算出来next[j]=k, 有P[k]=p[j]!=S[i], 所以完全没必要把j回溯到k, 我们继续求K' = next[k], 这时候有P[K']=S[i]='a'. 所以方法是:一直求k=next[k], 直到k=-1或者P[K]!=p[j], 实际上我们每一个next[j]都是这样 求出来的,所以只需要一步k=next[k]即可。
用上述的规则把前面的4个例子改下如下:
好了,其实代码也是很直观的:
void get_next(char *P, int n[])
{
int i=0,k=-1;
int l=strlen(P);
n[0]=-1;
while(i<l-1)
{
if(k==-1 || P[i]==P[k]){
++i;++k;
if(P[i]!=P[k])
n[i]=k;
else
n[i]=n[k];
}
else
k=n[k];
}
}
7. kmp匹配程序
最后kmp_index函数:
int kmp_index(char *s, char *p)
{
if(s==NULL || p==NULL) return -1;
int pl = strlen(p);
if(pl==0) return -1;
int *n=(int *)malloc(sizeof(int)*pl);
get_next(p,n);
int i=0,j=0;
while(s[i]!='\0' && p[j]!='\0')
{
if(s[i]==p[j])
{
i++;j++;
}else
{
if(n[j]!=-1){
j=n[j];
}else{
j=0;
++i;
}
}
}
free(n);
if(p[j]=='\0')
return i-j;
else
return -1;
}