先看下图:
从图中,我们可以很容易的发现,S不必回溯到S[1]的位置,T也不必回溯到T[0]的位置,因为前面的字符,S和T中都是相等的。如果S不回溯的话,那T该怎么办呢?我们也可以很容易的发现,S中5、6、7号字符和T中0、1、2号字符是相等的。故T直接回溯到T[3]的位置即可。此时我们就省去了很多不必要的回溯和比较。那么这些都是我们从图中直观得出的结论,如果换做其他字符,我们又如何知道T该回溯到几号字符呢?
先看看KMP算法的思想:假设在模式匹配的进程中,执行T[i]和W[j]的匹配检查。若T[i]=W[j],则继续检查T[i+1]和W[j+1]是否匹配。若T[i]<>W[j],则分成两种情况:若j=1,则模式串右移一位,检查T[i+1]和W[1]是否匹配;若1<j<=m,则模式串右移j-next(j)位,检查T[i]和W[next(j)]是否匹配。重复此过程直到j=m或i=n结束。(摘抄自百度百科) 不知其他人怎样,反正我是一看这些就晕。说的简单点,KMP算法的重点就是研究当某个字符不匹配时,T该回溯到什么位置去,以避免多余的回溯和比较过程。如果要确定字符不匹配时,T应该回溯到什么位置,那么我们就要对T字符串进行预处理。这里,我们用一个int型的 next 数组来标识T中当各个元素失配时,T 下标应该回溯到的位置。下面,还是用一张图片讲解:
图中,首先构造 Next 数组,构造过程见图解(这里讲解的简单了些,本文重点是理清KMP算法思路,故没有赘述,想细究的同学,自己谷歌一下吧)。构造完毕后,当S[8]和T[8]失配时,我们从next 数组可知,T应回溯到T[3]的位置,重新开始比较。样子有点像下面这样:
如果S[8]和T[3]再次失配,则继续回溯,即比较S[8]和T[0]。如果再次失配,T已无回溯元素可言,此时,S向后移动,即开始比较S[9]和T[0]……结束条件就是:到达字符串S或者T的结尾。若是S结尾,则返回-1.若是T结尾,则匹配成功。返回S中T串开始时的下标即可。
下面给出个小例子,仅供大家练习使用:
获得next 数组的算法
void get_next(char *T,int *next)
{
int i = 1;
int j = 0;
next[1] = 0;//第一个不匹配,则将子串数组移到下标为0这个位置
while(i < T[0])//判断头尾相同数 有一个相同则next[i]=2,两个则为next[i]=3
{
if(0 == j || T[i] == T[j])
{
i++;
j++;
next[i] = j;
}
else
{
j = next[j];
}
}
}
KMP匹配算法,与BF算法相似
KMP是子串动,主串不动
BF是主串动,子串不动,遇到不相等就回溯到第一个数
int index_KMP(char *s,char *t)
{
int i=1,j=1;
int next[255];
get_next(t,next);
while(i<=s[0]&&j<=t[0])
{
if(j==0||s[i]==t[j])
{
i++;
j++;
}
else
{
j=next[j];
}
}
if(j>t[0])
{
return i-t[0];
}
else
return 0;
}
完整代码:
#include <stdio.h>
#include<stdlib.h>
void get_next(char *T,int *next)
{
int i = 1;
int j = 0;
next[1] = 0;//第一个不匹配,则将子串数组移到下标为0这个位置
while(i < T[0])//判断头尾相同数 有一个相同则next[i]=2,两个则为next[i]=3
{
if(0 == j || T[i] == T[j])
{
i++;
j++;
next[i] = j;
}
else
{
j = next[j];
}
}
}
int index_KMP(char *s,char *t)
{
int i=1,j=1;
int next[255];
get_next(t,next);
while(i<=s[0]&&j<=t[0])
{
if(j==0||s[i]==t[j])
{
i++;
j++;
}
else
{
j=next[j];
}
}
if(j>t[0])
{
return i-t[0];
}
else
return 0;
}
int main()
{
char str1[255] = "xababaaabaaa";
char str2[255]="xaab";
str1[0]=11;
str2[0]=3;
int num;
num=index_KMP(str1,str2);
printf("%d\n",num);
return 0;
}
KMP算法的优化;
主要是改善next数组
void get_next(char *T,int *next)
{
int i = 1;
int j = 0;
next[1] = 0;//第一个不匹配,则将子串数组移到下标为0这个位置
while(i < T[0])//判断头尾相同数 有一个相同则next[i]=2,两个则为next[i]=3
{
if(0 == j || T[i] == T[j])
{
i++;
j++;
if(T[i]!=T[j]) //主要改变的地方 想想:如果是aa...aa则回溯的位置是一个a的next就可以
{
next[i] = j;
}
else
next[i]=next[j];
}
else
{
j = next[j];
}
}
}