KMP模式匹配

关于KMP模式匹配算法

在处理字符串中,我们总是需要判断一个主串S中,是否包含子串T,那么我们怎么能高效率地去做呢?

①            、朴素的模式匹配算法,所谓朴素,就是不讲技巧,暴力枚举,我们先看个例子,例如有个主串

S=”ABCDEFGGGQ”,我们需要去找其中其否含有子串T=”GGGQ”,如果用朴素的模式匹配,我们应该怎么弄呢?答案是:枚举s中的每一位

for (i=0;i<strlen(s);i++)

其中,枚举每一位后,去枚举子串T的每一位

for (j=0;j<strlen(t);j++)

然后,每位判断即可,不难看出,时间复杂度是O(n*m),很大。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main ()
{
    char s[100];
    char t[100];
    scanf ("%s%s",s,t);
    int len_s=strlen(s);
    int len_t=strlen(t);
    int i,j;
    for (i=0;i<len_s ;i++ )
    {
        for (j=0;j<len_t ;j++ )
        {
            if (s[i+j]!=t[j])
            {
                break;
            }
        }
        if (j==len_t)
        {
            printf ("%d--%d\n",i+1,i+len_t);
            break;
        }
    }
    if (i==len_s)
    {
        printf ("no respon\n");
    }
    return 0;
}
View Code

但是很容易发现,有些比较是可以省略的。考虑一下这个串S=”0000000001”和T=”0001”就是每次不成功的匹配都是在T的最后一个字符,但是,真的要这样嘛?我们开始判断的前3位(大家都是0),匹配成功,那么,以后我不能少判断一些吗?当然是可以得,这要看我们的KMP模式匹配了。

①            、KMP模式匹配:再看一个例子:s=”abcdefgab”和t=”abcdeL”,在这里,子串t中的每个元素都不相同,然而在匹配时,当t循环到’L’的时候判断不匹配,那么我就问了,既然t中的前5个”abcde”和s中的前5个相等,那么s中的i向下移动一位有用吗?就是,现在要比较s中的第二个字符b和t中第一个字符a是否相等,其实不用比较我就知道是不相等的了,为什么,你想啊,我们已经知道t中的各个字符都不相等,而s和t的前5位分别相等,说明什么?t中的第一个字符a肯定与s的2--5位不相等啊。!所以,这里可以省点时间。我们希望判断一次之后,i的值不回溯(就是i别让他变成2再去一位一位比较,直接跳跃),我们只移动子串t去比较。用上面的例子说吧,就是比较完“abcde”后,s中的f与t中的L不等,那么我就直接去判断s中的f与t中的a是否相等就可以了,前面那些比较是多余的。这里就有个问题,怎么确定j值的变化呢?i值好办,就是

for (i=0;i<strlen(s);i++)//因为它不回溯,就是说不会减少啦。

  

那么j值怎么办?如果我能预处理一个数组,使得每次我匹配不成功后,直接用j=next[j];就能够得到j值该去哪就好了。我也想啊,怎么得到啊?

Case:关于next数组的推导:

在这之前先要知道next数组的意义,next数组的作用是:帮助我们减少重复或者没必要的比较。何为没必要?就是:如果s=”000001”和t=”0001”,前面比较3个0相同,第四个失败了,然后,我让s中的第四个0与t中的那个比较呢?答案是第三个0,为什么?因为我们之前的0比较过 啊,相等啊,还用比较么?这里是减少了重复比较。而上面的s=”abcdefghi”和t=”abcdeL”中,i值不回溯,是为了减少没必要的比较。

知道了个大概之后,我们来看看next[]的数学定义

               0, if(j==1)

next[j] = max(k|”a1a2a3a4….a(k-1)”==”a(j-k+1)a(j-1)”);

                1,其他情况

需要注意的是,这个匹配是比较(k-1)位的,就是,如果有个串t=”AAAAL”,当j=1时,比较的为空串,所以next[1]=0;当j=2时,比较的只有A一个,所以属于其他情况next[2]=1;当j=3时,比较的是串”AA”,这个时候,前缀A和后缀A相等,请注意看上面公式,这个时候,k=2;所以,next[3]=2;当j=4的时候,注意啦,比较的串是”AAA”,那么,它的前缀和后缀是怎样相等的呢?很明显k=3的时候,前缀”AA”和后缀”AA”是匹配的,所以next[4]=3;注意啦,这里他们是公用了中间的一个A值的,同理next[5]=4;而next[6],没有与他匹配的,属于其他情况,所以next[6]=1;

此时的next数组是,下标从1开始的、

0

1

2

3

4

1

这里有个经验,如果得到前后缀一个字符相等,k=2;两个字符k=3;n个相等就是n+1;

next[]数组的意思是什么?根据上面的你能知道吗?就是,当第j位匹配不成功时,返回第next[j]为开始比较,为什么?就是能够减少重复比较和无用的比较啊。完整代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
void get_next(char *t,int *next)
{
    int i=1,j=0;
    next[1]=0;//固定的
    int len_t=strlen(t+1);
    while (i<len_t)
    {
        if (j==0||t[i]==t[j])
        {
            next[++i]=++j;
        }
        else j=next[j];
    }
    return ;
} 
void work ()
{
    char s[1000]={0};
    char t[1000]={0};
    scanf ("%s%s",s+1,t+1);
    int next[1002]={0};
    get_next(t,next);//得到next数组
    int i;
    int len_s=strlen(s+1),len_t=strlen(t+1);
    for (i=1;i<=len_t ;i++ )
    {
        printf ("%d ",next[i]);//先看看next数组的值
    }
    printf ("\n");
    int j;
    i=1;j=1;
    while (i<=len_s&&j<=len_t)
    {
        if (j==0||s[i]==t[j])
        {
            i++;
            j++;//比较下一位
        }
        else //否则的话,j去到最优的位置
        {
            j=next[j];
        }
    }
    if (j==len_t+1)
    {
        printf ("%d---%d\n",i-len_t,i-1);
    }
    else printf ("no\n");
    return ;
}
int main ()
{
    work ();
    return 0;
}
View Code

这篇文章写的很不详细,建议读者自己多推导next数组的值,理解好next数组的意思,就是较少重复比较和无用的比较,其中,推导next数组的值的时候,也用了next数组。

如果读者发现有错误,请帮忙指出,本人感激不尽。

 

posted on 2016-02-01 12:35  stupid_one  阅读(364)  评论(0编辑  收藏  举报

导航