KMP算法
KMP算法可以再O(m+n)的时间数量级上完成串的模式匹配操作,对于普通的模式匹配的改进之处在于:每当一趟匹配过程中出现字符比较不等时,不需回溯指向主串的指针,而是利用已经得到的“部分匹配”的结果将模式向右“滑动”尽可能远的一段距离后,继续比较。看下具体的例子:
i=3
第一趟匹配 a b a b c a b c a c b a b
a b c
j=3
i=7
第二趟匹配 a b a b c a b c a c b a b
a b c a c
j=5
i=11
第三趟匹配 a b a b c a b c a c b a b
(a) b c a c
j=2 j=6
设主串为‘s1s2...sn’,模式串为'p1p2...pm'
主要需要解决的问题是:当主串中第i个字符与模式中第j个字符“失配”时,主串中第i个字符(i 指针不回溯)应与模式中哪个字符再比较?
假设此时应与模式中第k(k<j)个字符继续比较,则模式中前k-1个字符的子串必须满足下列关系式
'p1p2...pk-1'='s(i-k+1)s(i-k+2)...s(i-1)' (1)
而已经得到的“部分匹配”的结果是
'p(j-k+1)p(j-k+2)...p(j-1)'='s(i-k+1)s(i-k+2)...s(i-1)' (2)
由此推出下列等式:
'p1p2...p(k-1)'='p(j-k+1)p(j-k+2)...p(j-1)' (3)
因此若模式串中存在满足式(3)的两个子串,则匹配仅需从模式中第k个字符与主串中第i个字符比较起继续进行
若令next[j]=k,则next[j]表明当模式中第j个字符与主串中相应字符“失配”时,在模式中需重新和主串中该字符进行比较的字符的位置,next函数的定义为:
next[j]={
0 当j=1时;
Max{k|1<k<j且'p1p2...pk-1='p(j-k+1)...p(j-1)''}(此集合不为空);
1 其他情况;
}
所以KMP的算法可以描述为:当匹配过程中产生“失配”时,指针i不变,指针j退回到next[j]所指示的位置上重新进行比较,并且当指针j退至0时,指针i和指针j需同时加1。即若主串的第i个字符和模式串中的第1个字符不等时,应从主串的第i+1个字符起重新进行匹配。
接下来要解决怎么求next[j]的问题。根据公式知:
next[1]=0, 设next[j]=k,则表明
'p1p2...pk-1'='p(j-k+1)...p(j-1)'
此时next[j+1]可能有两种情况,
1)若pk=pj,则表明在模式串中
'p1..pk'='p(j-k+1)...pj'
这就意味着next[j+1]=k+1,即next[j+1]=next[j]+1
2) 若pk!=pj,则表明在模式串中,'p1...pk'!='p(j-k+1)..pj'
此时可把求next函数值的问题看成是一个模式匹配的问题,整个模式串既是主串又是模式串,而当前在匹配过程中,已经有p(j-k+1)=p1,p(j-k+2)=p2,...p(j-1)=p(k-1),则当pj!=pk时,应将模式向右滑动至以模式中的第next[k]个字符和主串中的第j个字符相比较。若next[k]=k',且pj=p(k'),则说明在主串中第j+1个字符之前存在一个长度为k'(即next[k])的最长子串,和模式串中从首字符起长度为k'的子串相等。即
'p1p2...pk''='p(j-k'+1)...pj'(1<k'<k<j) (4)
这就是说,next[j+1]=k'+1 即 next[j+1]=next[k]+1
同理,若pj!=p(k'),则将模式继续向右滑动直至将模式中第next[k']个字符和pj对齐。。。依次类推,直至pj和模式中某个字符匹配成功或则不存在任何k'满足等式(4),则next[j+1]=1
例如,已求得前6个字符的next函数值,现求next[7],因为next[6]=3,又p6!=p3,则需比较p6和p1(next[1]=1),这相当于将子串模式向右滑动,而next[1]=0,所以next[7]=1,因为p7=p1,所以next[8]=2.
j 1 2 3 4 5 6 7 8
模式 a b a a b c a c
next[j] 0 1 1 2 2 3 1 2
上述定义的next函数尚有缺陷,例如模式'a a a a b'在和主串'a a a b a a a a b'匹配时。直接看图表示
j 1 2 3 4 5
模式 a a a a b
next[j] 0 1 2 3 4
nextval[j] 0 0 0 0 4
代码实现:
1 #include<stdio.h> 2 #define MAXSTRLEN 255 3 typedef unsigned char SString[MAXSTRLEN+1];//0单元存放串的长度 4 int Index(SString S,SString T,int pos) 5 { 6 //返回子串T在主串S中第pos个字符之后的位置,若不存在,则返回0 7 int i=pos; 8 int j=1; 9 while(i<=S[0]&&j<=T[0]) 10 { 11 if(S[i]==T[j]) 12 { 13 i++; 14 j++; 15 } 16 else 17 { 18 i=i-j+2; 19 j=1; 20 } 21 } 22 if(j>T[0]) 23 return i-T[0]; 24 else 25 return 0; 26 } 27 int Index_KMP(SString s,SString t,int pos,int next[]) 28 { 29 //利用模式串t的next函数求t在主串s中第pos个字符之后的位置 30 int i=pos; 31 int j=1; 32 while(i<=s[0]&&j<=t[0]) 33 { 34 if(j==0||s[i]==t[j]) 35 { 36 i++; 37 j++; 38 } 39 else 40 j=next[j]; 41 } 42 if(j>t[0]) 43 return i-t[0]; 44 else 45 return 0; 46 } 47 void get_next(SString t,int next[]) 48 {//求模式串t的next函数值并存入数组next 49 int i=1,j=0; 50 next[1]=0; 51 while(i<t[0]) 52 { 53 if(j==0||t[i]==t[j]) 54 { 55 i++; 56 j++; 57 next[i]=j; 58 } 59 else 60 j=next[j]; 61 } 62 } 63 void get_nextval(SString t,int nextval[]) 64 { 65 int i=1,j=0; 66 nextval[1]=0; 67 while(i<t[0]) 68 { 69 if(j==0||t[i]==t[j]) 70 { 71 i++; 72 j++; 73 if(t[i]!=t[j]) 74 nextval[i]=j; 75 else 76 nextval[i]=nextval[j]; 77 } 78 else 79 j=nextval[j]; 80 } 81 } 82 int main() 83 { 84 SString s,t; 85 int s_len,t_len,i,result; 86 printf("请输入主串的长度:\n"); 87 scanf("%d",&s_len); 88 s[0]=s_len; 89 printf("输入主串的元素:\n"); 90 for( i=1;i<=s_len;i++) 91 scanf("%d",&s[i]); 92 93 printf("请输入子串的长度:\n"); 94 scanf("%d",&t_len); 95 t[0]=t_len; 96 int next[4];//这个数组该怎样表示会更好?? 97 int nextval[4]; 98 printf("输入子串的元素:\n"); 99 for( i=1;i<=t_len;i++) 100 scanf("%d",&t[i]); 101 102 103 printf("普通的模式匹配结果:\n");//再加一个计算时间的就好了,虽然知道这个花的时间较长 104 result=Index(s,t,1); 105 printf("得到子串在主串中的位置为:%d\n",result); 106 107 printf("KMP算法模式匹配(next)的结果:\n"); 108 get_next(t,next); 109 result=Index_KMP(s,t,1,next); 110 printf("得到子串在主串中的位置为:%d\n",result); 111 112 printf("KMP算法模式匹配(nextval)的结果:\n"); 113 get_next(t,nextval); 114 result=Index_KMP(s,t,1,nextval); 115 printf("得到子串在主串中的位置为:%d\n",result); 116 }
运行结果:
关于代码中对数组长度的定义的问题,用指针动态分配内存。
1 #include<stdio.h> 2 #include<stdlib.h> 3 int main() 4 { 5 int n; 6 scanf("%d",&n); 7 int *p; 8 p=(int *)malloc(n*sizeof(int));//编译正确 9 int a[n];//编译错误。 10 }