KMP算法--究极版

转自翁振宇

 

字符串匹配---KMP算法    

算法实现一  

字符串匹配的关键在于减少匹配次数。而KMP算法的核心思想就是匹配数组,根据匹配数组的值来调度匹配字符串的索引减少多余的调度。

匹配数组就是字符串具有的相同前缀、后缀的最大值。比如对于字符串“ababacbbb”有:

t

a

b

a

b

a

c

b

b

b

\0

next

-1

0

0

1

2

3

0

0

0

-

j

0

1

2

3

4

5

6

7

8

-

1、  令next[0]=-1,从第二个字符(j=1)开始计算匹配数组next的值。

2、  j=1,字符b前面只有一个字符,该字符没有前缀、后缀因此next[1]=0。

3、  j=2,字符a前面的字符串“ab”前缀为a、后缀为b,并不相同。因此next[2]=0。

4、  j=3,字符b前面的字符串“aba”前缀有a、ab后缀有ba、a,相同的只有a长为1,因此next[3]=1。

5、  j=4,字符a前面的字符串“abab”前缀有a、ab、aba后缀有bab、ab、b,相同的只有ab长为2,因此next[4]=2。

6、  j=5,字符c前面的字符串“ababa”前缀有a、ab、aba、abab后缀有baba、aba、ba、a,相同的有a、aba,最长为3,因此next[5]=3。

7、  j=6,字符b前面的字符串“ababac”前缀有a、ab、aba、abab、ababa后缀有babac、abac、bac、ac、c没有相同的前后缀,因此next[6]=0。

8、  j=7,字符b前面的字符串“ababacb”前缀有a、ab、aba、abab、ababa、ababac后缀有babacb、abacb、bacb、acb、cb、b没有相同的前后缀,因此next[7]=0。

9、  j=8,字符b前面的字符串“ababacbb”前缀有a、ab、aba、abab、ababa、ababac、ababacb后缀有babacbb、abacbb、bacbb、acbb、cbb、bb、b没有相同的前后缀,因此next[8]=0。

当匹配字符串t与被匹配字符串s比较时,如果出现某个字符不相等,则根据next数组值调整匹配字符串t的下标(j=next[j])。举例来说t=“ababac”,s=“ababdababababac”:

 

1、  当s[i]与t[j]相同时直接递增下标,当s串与t串遇到不相等的字符时,比如i=4时,则根据next[]匹配数组值来调整t串的下标j。如果s[i]!=t[j]则令j=next[j]。如果改变j后s[i]       与t[j]仍然不相同,则继续修改j直到j=-1为止。当j=-1时,令i、j均加1,t串与s串重新进行匹配。如下图所示。

 

2、s[4]!=t[4],令j=next[4]=2,跳转到t[2]继续比较。

 

3、s[4]!=t[2],令j=next[2]=0,跳转到t[0]继续比较。

 

4、s[4]!=t[0],令j=next[0]=-1,则直接使i++,j++比较s[5]与t[0]。

 

5、从s[5]、t[0]开始连续5个字符相等,直到s[10]!=t[5],令j=next[5]=3,跳转到t[3]比较。

6、从s[10]、t[3]开始连续两个字符相同,直到s[12]!=t[5],令j=next[5]=3,跳转到t[3]比较。

 

7、从s[12]、t[3]开始,直到t串结束余下字符都相同,匹配成功。

 

 

代码实现

void getnext(char *t, int *next)    //获取字符串t的匹配数组值

{

int length=strlen(t);

next[0]=-1;    //匹配数组第一位取-1,作为一个标记点。

int j=-1;    //j=-1时可以寻找到标记点

int i=0;

while(j<length)

{

    if(j==-1 ||t[j]==t[i])    //j==-1或者前后t[j]==t[i]为真

    {

           j++;

              i++;

              next[i]=j;

}

else

       j=next[j];    //j根据next[j]的值往回跳转,直到出现字符相等或j为-1

}

}

 

int kmp(char *s, char *t)    //KMP算法实现函数

{

       int length_t=strlen(t);

       int *next=(int *)malloc(length*sizeof(int));

       getnext(t,next);    //调用去匹配数组值函数

       int length_s=strlen(s);

       int i=0;

       int j=-1;

       while(i<length_s && j<length_t)    //t串与s串的匹配查找过程

       {

              if(j==-1 || s[i]==t[j])

              {

                     j++;

                     i++;

}

else

       j=next[j];    //字符不相等是j值回溯

}

return ((j<length_t)?0:i-length_t);    //返回t串在s串中第一次出现的位置

}

 

 

算法实现二

在算法实现一中KMP处理字符串“aaaab”与“aaaacaaaab”的匹配时会会发现,j做了多次无用的跳转。因为“aaaab”的next[]为“-1 0 1 2 3”,j就会出现从3依次跳转到-1的情况,但是当j=3时发现‘a’与‘c’不相同那么就不应该继续比较后面的3个字符(都为‘a’)。因此这里需要对算法一中取匹配数组值的方法做一些调整。当出现“aaaab”这类情况时可以直接跳转到首个字符‘a’处。

t

a

a

a

a

b

c

b

b

b

\0

next

-1

0

1

2

3

0

0

0

0

-

next`

-1

-1

-1

-1

3

0

0

0

0

-

j

0

1

2

3

4

5

6

7

8

-

1、  根据next[]的值做一些调整。当t[i]==t[j]时,使t[i]的next[i]=next[j](i>j)。比如因为前面四个字符均为a,所以其next[i]均与next[0]相等。

2、  如果t[i]!=t[j],则直接将原来的next值保留(实际实现还是令next[i]=j(i>j))

代码实现

void getnext(char *t, int *next)

{

       int length=strlen(t);

       next[0]=-1;

       int i=0;

       int j=-1;

       while(i<length)

       {

              if(j==-1 || t[j]==t[i])

              {

                     j++;

                     i++;

                     if(t[j]==t[i])    //当出现相同字符,使后一个字符的next值等于前一个字符的next值

                            next[i]=next[j];

                     else

                            next[i]=j;

}

else

       j=next[j];

}

}

 

posted @ 2015-04-30 22:34  450606975  阅读(216)  评论(0编辑  收藏  举报