关于KMP算法的心得

最近在重新回顾算法部分,刚好看到KMP算法,把自己的一些理解记录下来。

KMP算法的核心,是一个被称为部分匹配表(Partial Match Table)的数组。我觉得理解KMP的最大障碍就是很多人在看了很多关于KMP的文章之后,仍然搞不懂next中的值代表了什么意思。这里我们抛开所有的枝枝蔓蔓,先来解释一下这个数据到底是什么。

对于字符串“abcabex”,它的next如下表所示:

 

就像例子中所示的,如果待匹配的模式字符串有8个字符,那么next就会有8个值。

我先解释一下字符串的前缀和后缀。如果字符串A和B,存在A=BS,其中S是任意的非空字符串,那就称B为A的前缀。例如,”Harry”的前缀包括{”H”, ”Ha”, ”Har”, ”Harr”},我们把所有前缀组成的集合,称为字符串的前缀集合。同样可以定义后缀A=SB, 其中S是任意的非空字符串,那就称B为A的后缀,例如,”Potter”的后缀包括{”otter”, ”tter”, ”ter”, ”er”, ”r”},然后把所有后缀组成的集合,称为字符串的后缀集合。要注意的是,字符串本身并不是自己的后缀。

Next数组储存的是模式串下标为j的位置前面[0,j-1]字符串的前缀集合与后缀集合的交集中最长元素的长度,例如,对于”aba”,它的前缀集合为{”a”, ”ab”},后缀 集合为{”ba”, ”a”}。两个集合的交集为{”a”},那么长度最长的元素就是字符串”a”了,长度为1,所以对于”aba”而言,它在next表中对应的值就是1。再比如,对于字符串”ababa”,它的前缀集合为{”a”, ”ab”, ”aba”, ”abab”},它的后缀集合为{”baba”, ”aba”, ”ba”, ”a”}, 两个集合的交集为{”a”, ”aba”},其中最长的元素为”aba”,长度为3。

所以我们要在主字符串”abcabfababex”中查找模式字符串”abcabex”,如果在j处匹配失败,根据next数组的性质我们可以知道,此时主字符串i之前next[j](记录的是前[0,j-1]位字符串的前缀集合与后缀集合的交集中最长元素的长度)位肯定和模式字符串0到j-1位相同。 这是因为主字符串在 i 位失配,也就意味着主字符串从 i−j 到 i 这一段是与模式字符串的 0 到 j 这一段是完全相同的。例如图示主字符串和模式字符串在e处失配,j为5,匹配的是“abcab”,此时next[j]的值为2,模式串中前缀和后缀几何交集最长元素的长度为2,主字符串中i指针之前的 2 位一定与模式字符串的第0位至第 2 位是相同的,即长度为 2的后缀与前缀相同。这样一来,我们就可以将这些字符段的比较省略掉。具体的做法是,保持i指针不动,然后将j指针指向模式字符串的 next[j] 位即可。

 

 KMP算法总体框架如下:

 1 int KMP(char * t, char * p,int *next)   //t为主字符串,p为子字符串
 2 {
 3     int i = 0;      //i为主字符串当前位置下的标值
 4     int j = 0;      //j用于子字符串当前位置下标值
 5 
 6     while (i < strlen(t) && j < strlen(p))
 7     {
 8 
 9         if (j == -1 || t[i] == p[j])  //两字母相等则继续
10         {
11             i++;
12             j++;
13 
14         }
15         else
16             j = next[j];     //j回溯到合适位置,i不变
17 
18     }
19 
20     if (j == strlen(p))
21         return i - j;
22     else
23         return -1;
24 }

有了上面的思路,我们就可以使用next加速字符串的查找了。我们看到如果是在 j 位 失配,那么影响 j 指针回溯的位置的其实是第 j  位的 next 值。

对于next值的推导,求next数组的过程完全可以看成字符串匹配的过程,即以模式字符串为主字符串,以模式字符串的前缀为目标字符串,一旦字符串匹配成功,那么当前的next值就是匹配成功的字符串的长度。

 

 

T=”abcdex”时(为了方便观看next中x表示-1)

 

1)当j=0时,next[0]=-1;

2)当j=1时,j由0到j-1就只有字符“a”,属于其他情况 next[2]=0;

3)当 j=2 时,j由1到j-1串是“ab”,显然“a”与“b”不相等,属其他情

况,next[3]=0;

4)以后同理,所以最终此T串的next[j]为(-1)00000.

 

2. T=“ababaaaba”

 

1)当j=0时,next[0]=-1;

2)当j=1时,同上next[1]=0;

3)当j=2时,同上next[2]=0;

4)当j=3时,j由0到j-1的串是“aba”,前缀字符“a”与后缀字符“a”相等,next[3]=1;

5)当j=4时,j由0到j-1的串是“abab”,由于前缀字符“ab”与后缀“ab”相等,所以next[4]=2;

6)当j=5时,j由0到j-1的串是“ababa”,由于前缀字符“aba”与后缀“aba”相等,所以next[5]=3;

7)当j=6时,j由0到j-1的串是“ababaa”,由于前缀字符“ab”与后缀aa”并不相等,只有“a”相等,所以next[6]=1;

8)当j=7 时,j由0到j-1的串是“ababaaa”,只有“a”相等,所以next[7]=1;

9)当j=8时,j由0到j-1的串是“ababaaab”,由于前缀字符“ab”与后缀“ab”相等,所以next[8]=2.

 

求next数组值的程序如下所示:

 1 void getNext(char * p, int * next)
 2 {
 3     next[0] = -1;
 4     int i = 0, j = -1;
 5 
 6     while (i < strlen(p))
 7     {
 8         if (j == -1 || p[i] == p[j])      /*p[i]表示后缀的单个字符*/
 9                                           /*p[j]表示前缀的单个字符*/
10         {
11             ++i;
12             ++j;
13             next[i] = j;
14         }
15         else
16             j = next[j];                //若字符不同,则j回溯
17     }
18 
19 }

总的代码:

 1 void getNext(char * p, int * next)
 2 {
 3     next[0] = -1;
 4     int i = 0, j = -1;
 5 
 6     while (i < strlen(p))
 7     {
 8         if (j == -1 || p[i] == p[j])      /*p[i]表示后缀的单个字符*/
 9                                           /*p[j]表示前缀的单个字符*/
10         {
11             ++i;
12             ++j;
13             next[i] = j;
14         }
15         else
16             j = next[j];                //若字符不同,则j回溯
17     }
18 
19 }
20 
21 
22 int KMP(char * t, char * p,int *next)   //t为主字符串,p为子字符串
23 {
24     int i = 0;      //i为主字符串当前位置下的标值
25     int j = 0;      //j用于子字符串当前位置下标值
26 
27     while (i < strlen(t) && j < strlen(p))
28     {
29 
30         if (j == -1 || t[i] == p[j])  //两字母相等则继续
31         {
32             i++;
33             j++;
34 
35         }
36         else
37             j = next[j];     //j回溯到合适位置,i不变
38 
39     }
40 
41     if (j == strlen(p))
42         return i - j;
43     else
44         return -1;
45 }
46 
47 int main(){
48     char s[]={"ababababca"};
49     char p[]={"abababca"};
50     int next[8];
51 //    cout<<strlen(p);
52     getNext(p,next);
53 
54     int num=KMP(s,p,next);
55     cout<<num;
56 }

猜你喜欢

转载自www.cnblogs.com/altar/p/13199912.html