KMP算法到底是什么原理就不说了,各种资料上讲的明明白白,下面我就如何用代码来实现做一下说明和记录.
KMP的核心思想就是,主串不回溯,只模式串回溯。而模式串匹配到第几位时失配,要回溯多少,由模式串本身来决定。
而求回溯数组主要思想是:
>模式串同时看做新的主串与模式串,自己匹配自己。
>每次匹配都能产生一个数组值,而这个数组值,就是模式串当前偏移量。
>如果模式串失配位置不是首位,那么,还要确定主串失配位置和模式串的首位是否匹配,因为上次匹配的时候,该位置的数组值已经赋了。
所以这次匹配不需要修改数组值。
>如果模式串失配位置是首位,那么,不需上述判断,直接主串后移一位。
代码流程如下:
1. 把模式串p分别作为主串pm和模式串pp进行匹配。
2. index[0] = 0;
3. 主串从pm[1] -> pm[strlen(p) - 1]循环,每次循环确定一个index[i]。
4. 一个内循环,用模式串(从pp[0]开始)的每一位去匹配主串(主串不回溯,所以它的位置由上次循环来决定)的每一位。
5. index[i] = 当前模式串的下标。
6. 每次循环中,如果匹配,内循环继续比较pm和pp的下一位;如果不匹配(失配),则分2种情况。
7. 情况1,失配时模式串pp的下标不为0,但是我们不确定失配时主串pm失配位置所在的字符是否与匹配串pp的首字符相同,所以,下次匹配要从失配位置开始。
但是,注意,因为在第5步我们在失配位置的index[i]已经赋值了,所以,有可能重复赋值。这一点可以做一些必要判断(如:index[i]是否首次赋值;本次循环是否是
为上次失配而进行的收尾工作)。
8. 情况2,失配时模式串pp的下标为0,所以我们不必担心7中的问题,主串下标直接移到下一位置即可。
由此,整个步骤就完成了。
代码如下:
static int *kmp(int *index, int length, char *pm){
for(int i = 0; i < length; i++){
index[i] = -200;
}
//1. pm是主串,pp是模式串
char *pp = pm;
//2. index[0] = 0
index[0] = 0;
//3. 因为index[0]已确定,因此主串pm从pm + 1处开始循环。
pm += 1;
while(*pm != '\0'){
int i = -1;
//4. 内循环,模式串pp从0开始按位匹配pm的每一位。
for(i = 0; i < length; i++){
//5. index[pm - pp + i] = i
if(index[pm - pp + i] == -200)//index[i]只允许赋值一次。为了防止第7步中【注意】中所说的情况
index[pm - pp + i] = i;
//6. 如果匹配,则继续循环,否则就是失配,分2种情况。
if(pm[i] != pp[i]){
//7.失配时模式串位置不为0,这时候pm移动至当前失配的位置。
if(i != 0){
pm += i;
}
//8. 失配时模式串位置为0,这时候pm移动到下一位置。
else pm++;
break;
}
}
}
return index;
}
由此,回溯数组确定了。那么kmp要如何使用这个数组呢?
>匹配主串与模式串,如果失配,主串和匹配串都要改变位置,分2种情况
>如果模式串失配位置是0,主串移动到失配的下一位置。如果失配位置不是0,则主串移动到失配位置。
>模式串移动到回溯数组中的指定位置。
代码如下:
int find(char *s, char *p){
char *src = s;//主串
char *psrc = p;//模式串
int pm_len = strlen(pm);
int *index = new int[pm_len];
kmp(index, pm_len, p);//回溯数组
while(*s != 0){
bool suc = true;
int i = -1;
for(i = 0; i < strlen(p); i++){
if(p[i] != s[i]){//失配, i表示模式串失配位置
//如果失配位置不为0,则移动主串到失配位置。
if(i != 0) s += i;//如果失配位置为0,则移动主串到失配位置的下一位。
else s++; //只要失配,模式串都移动到回溯数组中的指定位置。
p = psrc + index[i];
suc = false;
break;
}
}
}
//每次模式串匹配完之后,判断是否成功了。
if(suc){
delete []index;
return s - src;
}
}
delete []index;
return -1;
}