kmp(详解)

大佬博客:https://blog.csdn.net/lee18254290736/article/details/77278769 

对于正常的字符串模式匹配,主串长度为m,子串为n,时间复杂度会到达O(m*n),而如果用KMP算法,复杂度将会减少线型时间O(m+n)。


设主串为ptr="ababaaababaa";,要比较的子串为a=“aab”;


KMP算法用到了next数组,然后利用next数组的值来提高匹配速度,我首先讲一下next数组怎么求,之后再讲匹配方式。


next数组详解

首先是理解KMP算法的第一个难关是next数组每个值的确定,这个问题困恼我很长时间,尤其是对照着代码一行一行分析,很容易把自己绕进去。

定义一串字符串

ptr = "ababaaababaa";


next[i](i从1开始算)代表着,除去第i个数,在一个字符串里面从第一个数到第(i-1)字符串前缀与后缀最长重复的个数。


什么是前缀?

在“aba”中,前缀就是“ab”,除去最后一个字符的剩余字符串。

同理可以理解后缀。除去第一个字符的后面全部的字符串。


在“aba”中,前缀是“ab”,后缀是“ba”,那么两者最长的子串就是“a”;

在“ababa”中,前缀是“abab”,后缀是“baba”,二者最长重复子串是“aba”;

在“abcabcdabc”中,前缀是“abcabcdab”,后缀是“bcabcdabc”,二者最长重复的子串是“abc”;


这里有一点要注意,前缀必须要从头开始算,后缀要从最后一个数开始算,中间截一段相同字符串是不行的。


再回到next[i]的定义,对于字符串ptr = "ababaaababaa";

next[1] = -1,代表着除了第一个元素,之前前缀后缀最长的重复子串,这里是空 ,即"",没有,我们记为-1,代表空。(0代表1位相同,1代表两位相同,依次累加)。

next[2] = -1,即“a”,没有前缀与后缀,故最长重复的子串是空,值为-1;

next[3] = -1,即“ab”,前缀是“a”,后缀是“b”,最长重复的子串“”;

next[4] = 1,即"aba",前缀是“ab”,后缀是“ba”,最长重复的子串“a”;next数组里面就是最长重复子串字符串的个数

next[5] = 2,即"abab",前缀是“aba”,后缀是“bab”,最长重复的子串“ab”;

next[6] = 3,即"ababa",前缀是“abab”,后缀是“baba”,最长重复的子串“aba”;

next[7] = 1,即"ababaa",前缀是“ababa”,后缀是“babaa”,最长重复的子串“a”;

next[8] = 1,即"ababaaa",前缀是“ababaa”,后缀是“babaaa”,最长重复的子串“a”;

next[9] = 2,即"ababaaab",前缀是“ababaaa”,后缀是“babaaab”,最长重复的子串“ab”;

next[10] = 3,即"ababaaaba",前缀是“ababaaab”,后缀是“babaaaba”,最长重复的子串“aba”;

next[11] = 4,即"ababaaabab",前缀是“ababaaaba”,后缀是“babaaabab”,最长重复的子串“abab”;

next[12] = 5,即"ababaaababa",前缀是“ababaaabab”,后缀是“babaaaababa”,最长重复的子串“ababa”;


还有另外一种方法,我看的有的书上写着:

这里我们定义next[1] = 0 , next[1] = 1;


再分析ptr字符串,ptr = "ababaaababaa";

跟上一个的情况类似,


next[1] = 0 ,事先定义好的

next[2] = 1 ,事先定义好的

next[3] = 1 ,最长重复的子串“”;1代表没有重复,2代表有一个字符重复。

next[4] = 2 ,最长重复的子串“a”;追偿的长度加1,即为2.

next[5] = 3 ,以下都跟之前的一样,这种方法是最长的长度再加上一就可以了。

next[6] = 4

next[7] = 2

next[8] = 2

next[9] = 3

next[10] = 4

next[11] = 5

next[12] = 6


以上是next数组的详细解释。next数组求值 是比较麻烦的,剩下的匹配方式就很简单了。

next数组用于子串身上,根据上面的原理,我们能够推出子串a=“aab”的next数组的值分别为0,1,2.(按照我说的第二种方式算的)。


首先开始计算主串与子串的字符,设置主串用i来表示,子串用j来表示,如果ptr[i]与a[i]相等,那么i与j就都加1:


prt[1]与a[1]相等,i++,j++:

用代码实现就是

1 if( j==0 ||  ptr[i]==a[j])
2 {
3     ++i;
4     ++j;
5 }

 


ptr[2]与a[2]不相等

此时ptr[2]!=a[2],那么令j = next[j],此时j=2,那么next[j] = next[2] = 1.那么此时j就等于1.这一段判断用代码解释的话就是:

if( ptr[i]!=a[j])
{
      j = next[j];
}

加上上面的代码进行组合:

在对两个数组进行比对时,各自的i,j取值代码:

 1 while( i<ptr.length && j< a.length)
 2 {
 3      if( j==0 || ptr[i]==a[i] )
 4     {
 5           ++i;
 6           ++j;
 7 
 8           next[i] = j;
 9     }
10     else
11     {
12           j = next[j];
13     }
14 }

 


此时将a[j]置于j此时所处的位置,即a[1]放到j=2处,因为在j=2时出现不匹配的情况。



此时再次计算是否匹配,可以看出来a[1]!=ptr[2],那么j = next[j],即此时j = next[1] = 0;

根据上面的代码,当j=0时,执行++i;++j;

此时就变为:


此时ptr[3] = a[1],继续向下走,下一个又不相等了,然后“aab”向后挪一位,这里不再赘述了,主要的思想已经讲明白了。到最后一直到i = 8,j=3时匹配成功,KMP算法结束。整个过程就结束了。

talk is cheap,show me the code.

 1 #include<stdio.h>
 2 #include<string.h>
 3 #define N 1000005
 4 char s[N];
 5 char p[N];
 6 int next[N];
 7 int m,n;
 8 void getnext()
 9 {
10     int j=0,k=-1;
11     next[0]=-1;
12     while(j<m)
13     {
14         if(k==-1||p[j]==p[k])
15         {
16             j++;
17             k++;
18             next[j]=k;
19         }
20         else
21             k=next[k];
22     }
23 }
24 int kmp()
25 {
26     int i=0,j=0,ans=0;
27     getnext();
28     while(i<n)
29     {
30         if(j==-1||s[i]==p[j])
31         {
32             i++;
33             j++;
34         }
35         else
36             j=next[j];
37         if(j==m)
38             ans++;
39     }
40     return ans;
41 }
42 int main()
43 {
44     int t;
45     scanf("%d",&t);
46     while(t--)
47     {
48         scanf(" %s %s",&p,&s);
49         m=strlen(p);
50         n=strlen(s);
51         printf("%d\n",kmp());
52     }
53     return 0;
54 }

 




posted @ 2018-10-22 19:29  左手边五十米  阅读(545)  评论(0编辑  收藏  举报