预备知识
字符串的前缀,后缀和部分匹配值
前缀:指除最后一个字符以外,字符串的所有头部子串。
后缀:指除了第一个字符外,字符串的所有尾部子串。
部分匹配值:则为字符串的前缀和后缀的最长相等前后缀长度。
以 ababa 为例说明
- 'a’的前后缀均为空集,最长相等前后缀长度为0
'ab’的前缀为{a}, 后缀为{b},{a}∩{b}=∅,最长相等前后缀长度为0 - 'aba’的前缀为{a,ab}, 后缀为{ba,a}, {a,ab}∩{ba,a}={a},最长相等前后缀长度为1;
- 'abab’的前缀为{a,ab,aba}, 后缀为{bab,ab,b}, {a,ab,aba}∩{bab,ab,b}={ab},最长相等前后缀长度为2
- 'ababa’的前缀为{a,ab,aba,abab}, 后缀为{baba,aba,ba,a}, {a,ab,aba,abab}∩{baba,aba,ba,a}={a, aba},最长相等前后缀长度为3(即aba的长度)
- next[j] = 字符串S1…j-1的最长相等前后缀的长度 + 1
手算过程:
下面只是举例说明过程,按具体的某个next[ j ]来求解,其他位置的求解也类似。KMP的next值是自己和自己一位一位地移动后算出的每一位的next值,无需其他字符串。 规定 next[0]=0, next[1]=1, 其他位置可以按下面方法计算。
Eg1
求next[5]=1;第5个元素即e用红色标出
依次将串右移一位,因为假设第5位不匹配,则至少要右移一次。
下图中一共右移了4次,直到串的首字母 a 与 e同一列,说明此时在此过程中没有任何一个子串与 绿色的 bcd或bcd的后缀 有完全重合的
所以下一次则应和串的第一个字母比较,即得 next[5]=1
Eg2
求next[5]=2;第5个元素即 b 用红色标出
依次将串右移一位,因为假设第5位不匹配,则至少要右移一次。
下图中一共右移了3次,直到有一个子串与 绿色的 bca或bcd的后缀 有完全重合的, 也即第3次移动后,绿色的a和绿色的bca的后缀 a 完全重合,而第1,2次的粉色的均不与 绿色的 bcd或bcd的后缀 有完全重合的
所以下一次则应和串的第二个字母比较,即得 next[5]=2
这里因为第三次移动后就找找了,所以第四次的移动就不需要,下图中显示出来只是为了说明问题。
Eg3
求next[6]=4;第6个元素即 a 用红色标出
依次将串右移一位,因为假设第6位不匹配,则至少要右移一次。
下图中一共右移了2次,直到有一个子串与 绿色的 baba或baba的后缀 有完全重合的, 也即第2次移动后,绿色的aba和绿色的baba的后缀 aba 完全重合,而第1次的粉色的不与 绿色的 bcd 完全重合
所以下一次则应和串的第4个字母 b 比较,即得 next[5]=4
这里因为第2次移动后就找找了,所以余下几次的移动就不需要,下图中显示出来只是为了说明问题。
Eg4
求next[8]=7;第8个元素即 a 用红色标出
依次将串右移一位,因为假设第8位不匹配,则至少要右移一次。
下图中一共右移了1次,直到有一个子串与 绿色的 aaaaaa或aaaaaa的后缀 有完全重合的, 也即第1次移动后,绿色的 aaaaaa 和 绿色的 aaaaaa 完全重合
所以下一次则应和串的第二个字母比较,即得 next[8]=7
这里因为第1次就找找了,所以余下几次的移动就不需要,下图中显示出来只是为了说明问题。
KMP算法的实现
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//串的顺序存储 从数组下标为1开始存储字符
#define MAXLEN 255//预定义最大串长为255
typedef struct{
char ch[MAXLEN];//每个分量存储一个字符
int length;//串的实际长度
}SString;
//KMP算法求next值
void get_next(SString T, int next[]){
int i=1,j=0;//i依次指向 T 的每一位,
//j表示假设T.ch[i]不匹配,则下一次应和串的第j个字母比较
/*这里不容易理解:
只有满足T.ch[i]==T.ch[j],(为了说明问题,记m=++i; n=++j)
而后经过++i,++j, 才会执行到next[m]=n
说明前面的n-1个字符是完全重合的,
则当下一次T和其他所给的主串S进行匹配时,
若到m时出现不匹配情况
则下一次应和串T的第n个字符比较,即next[m]=n;
*/
next[1]=0;//规定next[1]为0
while(i<T.length){//@@@
if(j==0||T.ch[i]==T.ch[j]){//
++i;
++j;
next[i]=j;
}
else{
j=next[j];
}
}
}
void PrintString(SString S){
printf("\nString is:");
for(int i=1;i<=S.length;i++){
printf("%c",S.ch[i]);
}
printf("\n");
}
int Index_KMP(SString S,SString T){
int i=1,j=1;
int next[255];
get_next(T,next);
printf("\nnext array is:\n");
for(int k=1;k <= T.length;k++){//next数组从1开始存数
printf("%d ",next[k]);
}
printf("\n");
while(i<=S.length && j<=T.length){
if(j==0 || S.ch[i]==T.ch[j]){
//j=0说明此时S.ch[i]和模式串T的任何一个字符都不匹配,所以i,j都要向后移
++i;++j;//继续比较后继字符
}
else{
j=next[j];//S.ch[i]!=T.ch[j]说明下一次要和T.ch[ next[j] ]进行比较
}
}
if(j>T.length)
return i-T.length;//匹配成功
else
return 0;//匹配失败
}
int main(){
SString S1,S2;
//char str1[40]="hello?How are you world";
//char str2[40]="How are you";
char str1[40]="aaabaaaab";
char str2[40]="aaaab";
for(int i=0;i<strlen(str1);i++){
S1.ch[i+1]=str1[i];
}
S1.length=strlen(str1);
for(int i=0;i<strlen(str2);i++){
S2.ch[i+1]=str2[i];
}
S2.length=strlen(str2);
PrintString(S1);
PrintString(S2);
int x = Index_KMP(S1,S2);
printf("\nindex is %d\n",x);
return 0;
}
KMP算法的进一步优化
上图中S[4]和T[4]失配,如果用next数组,因为next[4]=3,则下一个需要用T[3]和S[4]比较,而T[3]为a,也失配,同理于next[3],next[2],next[1], 可以发现T的前三个字符和当前失配的a字符即T[4]是相同的,则再和S[4]相比较,必然会导致继续失配,这样的比较毫无意义,所以就产生了nextval数组。
如果出现上述情况即T[j]=T[ next[j] ],则需要再次递归,将next[j]修正为next[ next[j] ],
直到两者不相等为止,更新后的数组命为nextval.
Eg1
- j=2, T[2]=b, next[2]=1; b!= T[next[2]] 即(T[1] : a), 所以next[2]不变
- j=3, T[3]=a, next[3]=1; a = T[next[3]] 即(T[1] : a), 所以next[3]应修改为 next[next[3]]=next[1]=0
- j=4, T[4]=b, next[4]=2; b = T[next[4]] 即(T[2] : b), 所以next[4]应修改为 next[next[4]]=next[2]=1
- j=5, T[5]=a, next[5]=3; b = T[next[5]] 即(T[3] : b),所以next[5]应修改为next[next[5]]=next[3], 而next[3]在第二步中已经修改为0,故next[5]也修改为最新的next[3]即0
- j=6, T[6]=a, next[6]=4; a != T[next[6]] 即(T[4] : b), 所以next[6]不变
- 同理next[7]也不变
Eg2
下面代码可以运行出上面两个示例的 next 和 nextval 结果
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//串的顺序存储 从数组下标为1开始存储字符
#define MAXLEN 255//预定义最大串长为255
typedef struct{
char ch[MAXLEN];//每个分量存储一个字符
int length;//串的实际长度
}SString;
//KMP算法求next值
void get_next(SString T, int next[]){
int i=1,j=0;//i依次指向 T 的每一位,
//j表示假设T.ch[i]不匹配,则下一次应和串的第j个字母比较
/*这里不容易理解:
只有满足T.ch[i]==T.ch[j],(为了说明问题,记m=++i; n=++j)
而后经过++i,++j, 才会执行到next[m]=n
说明前面的n-1个字符是完全重合的,
则当下一次T和其他所给的主串S进行匹配时,
若到m时出现不匹配情况
则下一次应和串T的第n个字符比较,即next[m]=n;
*/
next[1]=0;//规定next[1]为0
while(i<T.length){//@@@
if(j==0||T.ch[i]==T.ch[j]){//
++i;
++j;
next[i]=j;
}
else{
j=next[j];
}
}
}
void PrintString(SString S){
printf("\nString is:");
for(int i=1;i<=S.length;i++){
printf("%c",S.ch[i]);
}
printf("\n");
}
//KMP算法求nextval值
void get_nextval(SString T, int nextval[]){
int i=1,j=0;//i依次指向 T 的每一位,
nextval[1]=0;//规定next[1]为0
while(i<T.length){//@@@
if(j==0||T.ch[i]==T.ch[j]){//
++i;
++j;
if(T.ch[i]!=T.ch[j]){
nextval[i]=j;
}
else{//T.ch[i]==T.ch[j]
//则若遇到T.ch[i]!=S.ch[i']时,也必有T.ch[j]!=S.ch[i'],
/*求next[i]就是为了T和S在匹配过程中发生了i之前的都匹配
而T.ch[i]!=S.ch[i'],为了避免 i’ 回溯,所以要看next[i]的值,
而当T.ch[i]==T.ch[j],也必有T.ch[j]!=S.ch[i'],
也即有nextval[i]=nextval[j]
*/
nextval[i]=nextval[j];
}
}
else{
j=nextval[j];
}
}
}
int Index_KMP(SString S,SString T){
int i=1,j=1;
int nextval[255];
int next[255];
get_next(T,next);
printf("\nnext array is:\n");
for(int k=1;k <= T.length;k++){//next数组从1开始存数
printf("%d ",next[k]);
}
printf("\n");
get_nextval(T,nextval);
printf("\nnextval array is:\n");
for(int k=1;k <= T.length;k++){//next数组从1开始存数
printf("%d ",nextval[k]);
}
printf("\n");
while(i<=S.length && j<=T.length){
if(j==0 || S.ch[i]==T.ch[j]){
//j=0说明此时S.ch[i]和模式串T的任何一个字符都不匹配,所以i,j都要向后移
++i;++j;//继续比较后继字符
}
else{
j=nextval[j];//S.ch[i]!=T.ch[j]说明下一次要和T.ch[ next[j] ]进行比较
}
}
if(j>T.length)
return i-T.length;//匹配成功
else
return 0;//匹配失败
}
int main(){
SString S1,S2;
//char str1[40]="hello?How are you world";
//char str2[40]="How are you";
char str1[40]="aaabaaaab";
//char str2[40]="aaaab";
//char str2[40]="ababaaaba";
char str2[40]="aaaaaaaab";
for(int i=0;i<strlen(str1);i++){
S1.ch[i+1]=str1[i];
}
S1.length=strlen(str1);
for(int i=0;i<strlen(str2);i++){
S2.ch[i+1]=str2[i];
}
S2.length=strlen(str2);
// PrintString(S1);
PrintString(S2);
int x = Index_KMP(S1,S2);
// printf("\nindex is %d\n",x);
return 0;
}