KMP算法之我见

这一篇用来记录KMP算法(看过一篇帖子说这是”看毛片”算法,邪恶了,呵呵)

一、首先我们要掌握BF(Brute-Force)算法,基本思想总结:

  主串  s  =  a b a c a b a b  i = 3

 模式串t  =  a b a b a        j = 3

i=3, j=3失配时,j要返回为0,i则要从上次开始处往后移一个位置开始重新匹配。

算法思想很容易理解,以下是代码实现:

int BF(char *s, char *t, int start)
{
    int i = start, j = 0, k;
    while(s[i] != '\0' && t[j] != '\0')
    {
        if(s[i] == t[j])
        {
            i++;
            j++;
        }
        else
        {
            i = i - j + 1;
            j = 0;
        }
    }
    if(t[j] == '\0')    k = i-j;
    else    k = -1;
    return k;
}

二、我目前对于KMP的理解程度不深,但是还是可以表述出来(^-^),大神莫喷。。。首先我们看上面那个例子:

  主串 s = a b a c  a b a b  i = 3

 模式串 t = a  b a b a        j = 3

当不匹配的时候,记住两点:

(1)主串不用返回,仍然从这个位置开始下次匹配;

(2)模式串需要返回,返回的位置由自身的信息决定;

1、先牢牢记住这两点,现在我们来理解为什么是会这样,先深入理解第一条:

首先我们来看一个简单的例子:

s = c d dc d c

t = c d c

当i=2,j=2失配时,按照BF算法我们应该比较s1和t0,但是t0 != t1,s1 = t1,所以s1 != t0,那么我们比较s1和t0还有什么意义呢?所以让我们直接比较s2和t0吧!

再看上面的那个例子:

  主串s = a b a ca b a b  i = 3

 模式串t = a b a b          j = 3

当i=3,j=3失配时,按照BF算法我们应该比较s1和t0,但是t0 != t1,s1  =t1,所以s1 != t0,

我们就会去比较s2和t0,但是t0=t2,s2=t2,所以s2=t0,那么我们就可以直接比较s3和t1了。

上面两种情况得出我们可以看出,s串的下标规律了:

主串不用返回,仍然从这个位置开始下次匹配;


2、从上面的分析中,我们看出了s串移动的规律,那么t串移动的规律呢?

对于第一种情况,回到了t0,第二种情况却回到了t1,这其中有什么规律吗?当然,这就是KMP的核心所在:

根据模式串自身的信息来决定它的回溯方式(可能专业的说法不是这样^-^,这是我自己的理解)

为此我们引入两个很关键的概念:

(1)前缀,后缀

(2)数组next[j]

前缀和后缀不用多说吧,如s = 12345,1234是前缀,2345是后缀。

Next是我们自己取名的,它就记录了我们一直所说的模式串的自身信息,next[j]和我们所说的前缀和后缀的关系是:

在模式串"t0t1...tj-1"中,其中一个以t0为首字母,另一个以tj-1为末字符,满足t0t1...tk-1=tj-ktj-k+1tj-1,且这样的相等字串是所有这种相等字串中最长的,则这个长度就是我们上面的next[j]的取值,我们规定,这样的字符串的长度小于模式串本身,所以0<k<j。

同时我们来对next做一个总结:

            Max{k|0<k<j且t0t1...tk-1=tj-ktj-k+1tj-1}(坑爹连大括号都打不出来)

Next[j]=    0,          其他情况

            -1,         当j = 0的时候

(有的人会问,为什么next就长成这样呢?同学,你想多了,这就是它的定义,KMP对next还有其它的定义,这还要考虑效率等方面的因素,但是我觉得这种比较好理解)

有了定义,我们来看一个例子:

j 0 1 2 3 4
t[j] a b a b a
next[j] -1 0 0 1 2

3、我们对模式串引入了两个概念,但是还没有深入探讨next[j]和模式串回溯位数的具体关系,下面我们深入看看:

(1)当next[j]= j-1时,有t0...tj-2=t1...tj-1,而si != tj失配时有si-j...si-1=t0...tj-1,所以t0...tj-2涵盖在si-j...si-1中了,下次我们直接比较si和tj-1吧!看看这个下标j-1是不是就是我们next[j]的值呢?

(2)当next[j] =j-2时,有t0...tj-3=t2...tj-1,失配时si-j...si-1=t0...tj-1,所以t0...tj-3仍然涵盖在si-j...si-1中,我们直接比较si和tj-2吧!看看这个下标j-1是不是就是我们next[j]的值呢?

(3)当next[j] = 1时,有t0=tj-1,失配时有si-j...si-1=t0...tj-1,所以t0已经等于si-1,所以我们直接比较si和t1吧。

再配上一个图:

a b a b ca b a b a      i = 4

a b a b a             j = 4

当j = 4时,next[j]= 2,下次从j = 2开始匹配

a b a b ca b a b a

    a b a b a 

s中si前的字符串涵盖了t0和t1这样的信息,直接比较si和t[next[j]]吧!

综上所述,我们对next[j]的作用深刻理解了一下

下面我们来看KMP算法的实现:

int KMP(char *s, int start, char *t, int next[])
{
    int i = start, j = 0, k;
    while(s[i] != '\0' && t[j] != '\0')
    {
        if(s[i] == t[j])
        {
            i++;
            j++;
        }
        else if(j == 0) i++;
        else j = next[j];
    }
    if(t[j] == '\0')  k = i-j;
    else
        k = -1;
    return v;
}

那么所谓的next应该怎么来求呢?

next的求解和KMP的思路是一样的,我就直接贴上代码吧:

void GetNext(char *s, int next[])
{
    int j = 1, k = 0;
    next[0] = -1;
    next[1] = 0;
    while(s[j] != '\0')
    {
        if(s[j] == s[k])
        {
            next[j+1] = k+1;
            k++;
            j++;
        }
        else if(k == 0) { next[j+1] = 0; j++;}
        else k = next[k];
    }
}

加上测试函数,整个KMP的算法如下:

#include <stdio.h>
void GetNext(char *s, int next[])
{
    int j = 1, k = 0;
    next[0] = -1;
    next[1] = 0;
    while(s[j] != '\0')
    {
        if(s[j] == s[k])
        {
            next[j+1] = k+1;
            k++;
            j++;
        }
        else if(k == 0) { next[j+1] = 0; j++;}
        else k = next[k];
    }
}

int KMPIndex(char *s, int start, char *t, int next[])
{
    int i = start, j = 0, k;
    while(s[i] != '\0' && t[j] != '\0')
    {
        if(s[i] == t[j])
        {
            i++;
            j++;
        }
        else if(j == 0) i++;
        else j = next[j];
    }
    if(t[j] == '\0')  k = i-j;
    else
        k = -1;
    return k;
}
int main()
{
    char *s = "121231456";
    char *t = "1231";
    int next[5] ={0};
    GetNext(t, next);
    int m = KMPIndex(s, 0, t, next);
    printf("the answer is:%d\n",m);
    return 0;
}
写完了,四个小时!!!累P了,csdn的这个编辑器能不能改改啊,各种不方便



你可能感兴趣的:(KMP)