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 }
View Code

 

运行结果:

   关于代码中对数组长度的定义的问题,用指针动态分配内存。

 

 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 }

 

 

             

                        

posted @ 2013-06-05 13:18  wj704  阅读(282)  评论(0编辑  收藏  举报