码迷,mamicode.com
首页 > 编程语言 > 详细

浅谈KMP算法及其next[]数组

时间:2016-04-10 20:58:46      阅读:288      评论:0      收藏:0      [点我收藏+]

标签:

KMP算法是众多优秀的模式串匹配算法中较早诞生的一个,也是相对最为人所知的一个。

算法实现简单,运行效率高,时间复杂度为O(n+m)(n和m分别为目标串和模式串的长度),比蛮力算法的O(nm)快了许多。

理解KMP算法,关键是理解其中的精髓——next[]数组。

 

(统一起见,下文将目标字符串记作obj,将模式字符串记作pattern,这与后面的程序代码是一致的)

我们给一个字符串S定义一个next值,记作next(S),next(S)=n表示:

(1)S的前n个字符构成的前缀,和后n个字符的后缀是相等的

(2)n是满足(1)条件的极大值。

如果不存在一个满足(1)条件的n,那么记next(S)=0。

例如,字符串“GACC”的next值为0,“GACCGG”的next值为1(极大前(后)缀为“G"),

“GACCGGACC”的next值为4(极大前(后)缀为”GACC“),“GAGAG”的next值为5。(极大前(后)缀就是它本身)。

而next[]数组,记录的则是pattern的所有前缀的next值。

 

next数组的作用,就是在模式匹配的过程中,尽可能减少模式串的偏移量。

下令obj=”GACGAACGACCGACGACGCCGACGAC“(O__O"…),pattern=”GACGCCG“。

模式匹配的流程如下:

设立两个”游标“i和j,分别指向obj串和pattern串当前正在检查的字符,初始时令i=j=0。

首先检查第一个字符,若obj[i]==pattern[j],那么i++,j++。直到遇到obj[i]!=pattern[j]的情形。

GACGAACGACCGACGACGCCGACGAC

GACGCCG

前4个字符检查通过,但第5个字符(i=j=4)出了问题。

朴素的做法是让i和j都回退,对于此例,回退至i=1,j=0,然而这样无疑会做许多重复的计算。

而KMP算法的做法则是:只移动pattern。而移动的方法,则关系到算法的效率甚至正确性。

我们的原则是:保证正确的前提下,使得重复判断的次数尽可能少。

最佳的做法是将j移动到next[j-1]的位置。

(1)由next的性质(前缀等于后缀的极大值)可知,这样可以省去尽可能多的判断

(2)并且可以保证这样做是正确的

此时j=4,而next[j-1]=next[3]=1,所以令j=1,再次进行比较。

GACGAACGACCGACGACGCCGACGAC

------GACGCCG

比对通过。令i=5,j=2。

GACGAACGACCGACGACGCCGACGAC

------GACGCCG

next[1]=0,所以令j=0。

GACGAACGACCGACGACGCCGACGAC

----------GACGCCG

即使回退到第一位也没有出现字符相等的情形。而我们已经无法回退了。

此时只能令i++,表示前面的部分检查无果,继续向后检查。

(你也可以理解成,让obj相对于pattern运动)

GACGAACGACCGACGACGCCGACGAC

--------------GACGCCG

重复上述的过程到了这里(i=10,j=3),next[2]=0

GACGAACGACCGACGACGCCGACGAC

--------------------GACGCCG

pattern无法回退了,所以向前检查。

GACGAACGACCGACGACGCCGACGAC

----------------------GACGCCG

i=15,j=4,next[3]=1

GACGAACGACCGACGACGCCGACGAC

----------------------------GACGCCG

回退之后比对成功,i++,j++,重复这一过程,直到……

 

GACGAACGACCGACGACGCCGACGAC

 

----------------------------GACGCCG

至此我们便利用next数组完成了模式串匹配的过程。

可以看到,next数组使得匹配过程中少了很多不必要的计算,整个匹配过程显得高效利落。

 

那么问题来了,怎样高效地求next数组?暴力是肯定行不通的。

事实上,next数组的求法和上述KMP算法的步骤惊人地相似,甚至可以说,求next数组的过程就是一个自我匹配的过程。

也就是:求next[j],就是让pattern[0..j]和pattern[0..x]进行上述的匹配过程。这里x=next[j-1]。next[j]=能够匹配成功的子串的最大长度。

下令pattern=”GACCGGACCGA“

首先令next[0]=0,易知next[1]=0。

之后,由于pattern[2]!=pattern[0],所以next[2]=0。同理next[3]=0。

由于pattern[4]==pattern[0],所以next[4]=0+1=1。

这里的0是next[3]的值。表示字符pattern[4]可以接到next[3]对应的后缀之后。

由于pattern[5]!=pattern[1],pattern[5]==pattern[next[0]]==pattern[0],所以next[5]=1。

详情如下:

GACCGG

--------GA    【 用pattern[0..1]匹配pattern[0..5],这里的1是next[4] 】

匹配不通过,所以令”模式串“的游标移至next[1]=0处(加了引号是因为这里的”模式串“是对pattern本身进行匹配)

GACCGG

----------GA

类似地,可得:next[6]=next[5]+1=2,next[7]=next[6]+1=3,next[8]=next[7]+1=4,next[9]=next[8]+1=5。

next[10]的求法如下:

GACCGGACCGA

----------GACCGG    【 next[4]=1 】

 

GACCGGACCGA

------------------GACCGG

故next[10]=2。

 

代码如下:

技术分享
 1 #include <cstdio>
 2 #include <cstring>
 3 
 4 const int maxL=1000005;
 5 
 6 char obj[maxL];
 7 char pattern[maxL];
 8 
 9 void input()
10 {
11     scanf("%s%s",obj,pattern);
12 }
13 
14 int next[maxL]={0};
15 
16 int getNext()
17 {
18     next[0]=0;
19     int len=strlen(pattern);
20     for(int i=1;i<len;i++)
21     {
22         int k=next[i-1];
23         while(k>=0 && pattern[i] != pattern[k]) k=k?next[k-1]:-1;
24         next[i]=k+1;
25     }
26     return len;
27 }
28 
29 int kmp() //searching for the first place that pattern appears in obj
30 {
31     int lenObj=strlen(obj);
32     int lenPattern=getNext();
33     for(int i=0,j=0;i<lenObj;i++)
34     {
35         while(j>=0 && obj[i] != pattern[j]) j=j?next[j-1]:-1;
36         if(lenPattern==(++j)) return i-j+1;
37     }
38     return -1; //not found
39 }
40 
41 int main()
42 {
43     input();
44     printf("%d\n",kmp());
45     return 0;
46 }
View Code

(我们用-1作为循环终止的条件)

附一道简单的练习题,求pattern在obj中出现的次数。

技术分享
 1 #include <cstdio>
 2 #include <cstring>
 3 
 4 const int maxL=1000005;
 5 
 6 char obj[maxL];
 7 char pattern[maxL];
 8 
 9 void input()
10 {
11     scanf("%s%s",pattern,obj);
12 }
13 
14 int next[maxL]={0};
15 
16 int getNext()
17 {
18     next[0]=0;
19     int len=strlen(pattern);
20     for(int i=1;i<len;i++)
21     {
22         int k=next[i-1];
23         while(k>=0 && pattern[i] != pattern[k]) k=k?next[k-1]:-1;
24         next[i]=k+1;
25     }
26     return len;
27 }
28 
29 int kmp()
30 {
31     int ans=0;
32     int lenObj=strlen(obj);
33     int lenPattern=getNext();
34     for(int i=0,j=0;i<lenObj;i++)
35     {
36         while(j>=0 && obj[i] != pattern[j]) j=j?next[j-1]:-1;
37         if(lenPattern==(++j)) { ++ans; j=next[j-1]; }
38     }
39     return ans;
40 }
41 
42 int main()
43 {
44     int T; scanf("%d",&T);
45     while(T--)
46     {
47         input();
48         printf("%d\n",kmp());
49     }
50     return 0;
51 }
Problem:POJ P3461

 

算法这东西,难者不会,会者不难。如果理解时遇到了瓶颈,不妨用笔实验一下,总结规律,也许就能悟出算法的思想。

ps.推荐另一篇博文:http://blog.csdn.net/OIljt12138/article/details/51107585,可以作为阅读本文的对照参考

浅谈KMP算法及其next[]数组

标签:

原文地址:http://www.cnblogs.com/Onlynagesha/p/5375333.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!