KMP算法详解与模板

来源:互联网 发布:哈利波特英文版 知乎 编辑:程序博客网 时间:2024/06/03 18:24

KMP算法是一个字符串匹配的经典算法,但同样的也非常难懂,网上也有很多关于KMP的文章,不过各有各的讲法,前缀数组的求法有的是           next【0】=-1,有的是next【0】=0,这也导致了初学者看的时候比较困惑,本弱刚刚学的时候看了一些博客,又在b站上看了一个讲解KMP算法的视频,决定写一个笔记记录一下这个算法,使印象深刻一下;

KMP算法主要是通过待匹配的字符串前缀和后缀相同使主串无需从头匹配,减少运行时间,提高效率;下面举例说明。

主串(1串):

a

b

c

x

a

b

c

d

a

b

x

a

b

c

d

a

b

c

d

a

b

c

y

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

 

 待匹配串(2串):

 

a

b

c

d

a

b

c

y

1

2

3

4

5

6

7

8

首先,拿2串的第一个字母和2串的第一个字母进行匹配,发现可以匹配成功,则同时后移一位,当匹配到第三位时,1串是‘x’,2串是‘d’,发现此时匹配不成功了,这时,我们观察在待匹配串的4之前的子串是否存在某个前缀和后缀是相等的,我们发现不存在这个情况,那么只好将待匹配串从头开始匹配;此时拿2串的1号字母‘a’和1串的5号字母‘a’进行匹配,匹配成功,同时都后移一位继续匹配,直到1串是11号字母‘x’时,2串是7号字母‘c’,这时匹配不成功,继续在待匹配串中找前后缀,这时我们有新发现了:

      在待匹配串7号之前的子串中,‘ab’既是前缀又是‘后缀’,那么还需不需要将待匹配串从头开始进行匹配呢,当然不需要。我们可以将待匹配串从3号元素开始继续与主串的下一个字母来匹配,为什么可以跳过‘ab’呢,这是因为,在待匹配串的7号与主串的11号匹配失败时,那么他们前面的字母一定都是匹配成功的,主串11号之前的元素‘ab’一定是可以和待匹配串7号之前的‘ab’是匹配的,而对作为7号之前子串的后缀‘ab’来说,它也是子串的前缀,那么也就是说,既然主串中11号之前的‘ab’可以和子串的后缀匹配的上,同样的一定也可以和前缀匹配的上,毕竟前缀等于后缀,所以我们就可以跳过这个前缀,从‘ab’后面的一个字母开始匹配。(KMP可以提高效率的关键,可以认真想想)。

下面用A代表主串,B代表待匹配串。

      接下来,开始A11和B3的匹配,发现匹配不上,而且B3之前没有相同的前后缀,那么将B串从头开始继续与A串匹配;A12和B1匹配,匹配成功,继续下去,一直到A19与B8匹配时,失败了;开始找B8之前有没有相等的前后缀,发现‘abc’是一个相等的前后缀,那么,将B串从4号元素开始继续和A串19号元素匹配,匹配成功,继续下去,一直到B串结束都匹配成功,那么可以确定B串就是A串的子串。这个过程的时间复杂度是O(n)。


    原理说完了,那么该如何高效的判断子串中是否还有前后缀呢,如果含有的话,前后缀长度又是多少呢,下面说这个问题。

在这里,我用前缀数组next【0】=0这个版本来计算;

下面给一个例子,

0

1

2

3

4

5

6

7

a

b

c

d

a

b

c

a

 0

 

 

 

 

 

 

       这是一个待匹配串s,我们用j指向0,i指向1,同时next[0]=0;我们发现s[i]!=s[j],那么就在1下面写个0,即next[1]=0;同时i往后移一位到2;s[i]依旧不等于s[j],next[2]=0;

同理,next[3]=0;下面i到4,此时s[i]=s[j];那么就将j的值加1给next[4],此处这个1,代表在4之前的子串中(包含4),最长相等的前后缀长度为1,即‘a’,同样的0代表没有前后缀;01234567

a

bcdabca00001   

      同时将i往后移一位,j也往后移一位;此时i在5处,j在1处,s[i]=s[j],继续将j的值加1给next[5],i,j都往后移一位,到6也一样;一直到i在7处,j在3处,此时s[j]!=s[i],这时候就有些麻烦了,我们需要找到j前面的字母对应的next数组的值,在这个例子中,j前面是2,对应的next数组值是0,这是就把j的值更新为0再次与i匹配,发现s[i]=s[j],此时就将j的值加1给next[7],前缀数组计算完毕,得到前缀数组如下。(注意,如果把j更新为j前面的数组对应的值后,s[i]仍然不等于s[j],那么就需要继续以同样的方法更新j的值)

0

1

2

3

4

5

6

7

a

b

c

d

a

b

c

a

 0

0

0

0

1

2

3

1

下面再给个例子:

0

1

2

3

4

5

6

7

8

a

a

b

a

a

b

a

a

a

 

 

 

 

 

 

 

 

 这个前缀数组求出的结果就为:

0

1

2

3

4

5

6

7

8

a

a

b

a

a

b

a

a

a

0

1

0

1

2

3

4

5

2


这个过程的代码实现如下:

void Creat_next(char *str){    int j=0;    int len=strlen(str);    next[0]=0;    for(int i=1; i<len; i++)    {        while(j>0&&s[i]!=s[j])//j>0保证数组不会越界;            j=next[j-1];        if(s[i]==s[j])            j++;        next[i]=j;    }}

注意next数组是一个比较难以理解的地方,它的用处在很多题目中都体现有。如果想要深入了解next数组的意义,可以做下poj2752、poj2406、poj1961、hdu3336;

链接:poj2752,:http://poj.org/problem?id=2752

           poj2406:http://poj.org/problem?id=2406

           poj1961:http://poj.org/problem?id=1961

           hdu3336:http://acm.hdu.edu.cn/showproblem.php?pid=3336;

      next前缀数组的实现已经完成了,下面说下KMP的代码实现,这里就不再多说了,之前开头已经说过怎么匹配的了,主要是next数组的构造比较难理解,下面贴上KMP实现的代码;

void KMP(char *str1,char *str2){    int len2=strlen(str2),len1=strlen(str1);//str1是待匹配串,str2是主串;    int j=0,ans=0;    for(int i=0; i<len2; i++)    {        if(str1[j]!=str2[i]&&j>0)            j=next[j-1];        if(str1[j]==str2[i])            j++;        if(j==len1)//相等说明找到子串;            return ;    }}
ok,KMP算法大致已经说完了,本笔记主要依据b站上讲解的KMP算法来写的,只是记录一下学习过程,如有错误还请指出来,不胜感激~


1 0
原创粉丝点击