这里先给出之前我参考的博客网址,以及参考的书籍是数据结构(严蔚敏)。
参考代码的网址
这里我总结一下我的思路。
先介绍一些基本概念
- 主串:这里指的是要匹配的字符串
- 模式串:需要在主串中寻找的字符串
- KMP匹配算法的重点在于利用模式串自身的重复部分,在匹配中消除那些重复的匹配过程。
下面约定
- 字符串和next数组的下标从1开始(人为规定)
- i为指向主串的指针,j和k为指向模式串的指针
- T代表为模式串数组,S代表主串数组
- ==表示左右相等,!=表示左右不相等
next数组的求解
- next[1] = 0
我们约定next数组的第一个位置值为0,代表的是模式串的第一个字符失配(也就是连第一个字符都不匹配),这时就从主串当前失配字符的下一个字符开始重新匹配(i增1),模式串当然也是从头开始(j为1)。
- 已知next[j] = k
所以:’T[1]…T[k-1]’ == ‘T[j-k+1]…T[j-1]’
这里解释一下:上面的表示模式串的前缀长度为k-1的字符串和从j往前数k-1长度的字符串是相等的。
这时会出现两种情况:
第一种:T[k] == T[j]
所以:’T[1]…T[k]’ == ‘T[j-k+1]…T[j]’
因为:’T[1]…T[k-1]’ == ‘T[j-k+1]…T[j-1]’的时候next[j] = k
所以:’T[1]…T[k]’ == ‘T[j-k+1]…T[j]’的时候next[j+1] = k+1 = next[j] + 1
这里会出现一个问题:如果S[i] != T[j],模式串的第j个字符失配,那么我们就会让S[i]和T[next[j]]进行比较,但是我们这里是第一种情况T[next[j]] == T[j],所以当然也不等于S[i],这就等于做了一次无用功。
所以需要在上面的判断里面再加上一个判断,若T[k+1] == T[j+1],则next[j+1] = next[k+1],否则next[j+1] = k+1
这里举个例子:模式串ABAB,当k指向第一个位置A,j指向第三个位置A,它们俩相等,原本直接赋值next[j+1] = 2但是因为T[k+1] = T[j+1],所以改成next[j+1] = next[k+1] = 1。原本ABAB的next数组为0112,现在变为0101。
第二种:T[k] != T[j]
举个例子:模式串abacdababc,其中k为4,j为9。分别截取出来看就是abac和abab
因为:T[k] != T[j],可以看出abac的前面的a和abab的倒数第二个a相同,就直接就可以从abac的第二个b开始进行比较
所以:这里只需将k改为2也就是k = next[k],让abac第2个和abab最后一个元素对其即可。再次和T[k]比较,若相同则T[j+1] = next[k]+1;否则,再次执行j = next[j]直到j = next[1] = 0为止,这样T[j+1] = next[1]+1 = 1
总结一下就是:
- T[1]…T[k]’ == ‘T[j-k+1]…T[j],next[j] = k;
- 在1的条件下
- T[k]==T[j]且T[k+1] == T[j+1],next[j+1] = next[k+1]
- T[k]==T[j]且T[k+1] != T[j+1],next[j+1] = k+1
- T[k]!=T[j],k = next[k]
- next[1] = 0
因为这些都是推出来的,就像数学的定理,可以因为所以的一步步推出来,所以当成公式或者转化成图像记忆都可以。
综合上述分析,程序思路就可以得出
- 将j置为1指向模式串的第一个元素,k为0表示从模式串第一个开始比较,next数组第一位为0
- 接下来当然就是一个循环遍历整个模式串
- 两个判断条件k是否为0以及T[k]是否等于T[j],若k为0表示又从头开始匹配,则直接将k和j增1,。若T[k]等于T[j]且T[k+1] == T[j+1],表示next[j+1] = next[k+1],若不等于,就按照正常的方法next[j+1] = k+1
- 每一次的判断都是为了求得next[j+1]的值,所以这里j需要增1,k增1前面已经讲过了
- 否则就是k = next[k]的情况
- 两个判断条件k是否为0以及T[k]是否等于T[j],若k为0表示又从头开始匹配,则直接将k和j增1,。若T[k]等于T[j]且T[k+1] == T[j+1],表示next[j+1] = next[k+1],若不等于,就按照正常的方法next[j+1] = k+1
下面给出改进前和改进后的c语言代码
改进前的代码
void get_next(SString T, int next[]){
int j = 1;
int k = 0;
next[1] = 0;
while(j<T[0]){
if(k==0 || T[j]==T[i]){
j++;
k++;
next[j] = k;
}else{
k = next[k];
}
}
return;
}
改进后的
void get_nextval(SString T, int nextval[]){
int j = 1;
int k = 0;
next[1] = 0;
while(j<T[0]){
if(k==0 || T[k]==T[j]){
j++;
k++;
if(T[k]!=T[j]){
nextval[j] = k;
}else{
nextval[j] = nextval[k];
}
}else{
k = next[k];
}
}
return;
}