KMP算法详解<一>

Kmp算法

Kmp算法相比BF算法的优化之处在于当s[i] != p[j]时,i不回退,只有j回退。

 

1.下面我们来论证一下i为什么不用回退

 

如上图,在匹配到i  = 7时失配,按照BF算法的思想,i’要回退到i’+1的位置,j要回退到j’+1的位置。这样的时间复杂度为O(m*n)

但由以上例子不难发现,其实i’并不需要回退。这是为什么呢?

因为回退后再比较时,显然s3!=p0, s4!=p1.......s6!=p3。既然这样,就没有必要回退i

有人会想,那要是一样呢?显然,都一样了那还比较干嘛,i当然也不需要回退。这样是不是感觉有点耍流氓?没办法,人家就是这么流氓。

2.我们已经知道i不需要回退,那么j呢?j要回退到哪里呢?相信聪明的你一定发现了,j是不需要回退到0的,因为s5 =p0, s6 =p1, 那么j只需要回退到下标为2的地方,接着比较久可以了。这时,你又该问了,我们人的肉眼可以看到j要回退到2,可是计算机要怎么知道呢?这个问题问的好,我将在下面解答你的疑问。

3.现在我们假设失配后,j要回退的位置为k, 猜想一下,k的值都和谁有关系呢?

下面跟我一起来见证奇迹:

聪明的你一定看出x等于i-k了,所以上式等于:

P0………………Pk-1  =  Si-k………………Si-1        ( 1 )

 由( 1 )可推出:

    P0………………Pj-1  =  Si-j………………Si-1         ( 2 )

缩小范围,得:

Px………………Pj-1  =  Si-k………………Si-1

x = j-k

Pj-k………………Pj-1  =  Si-k………………Si-1       ( 3 )

( 1 )( 3 )得:

P0………………Pk-1  =  Pj-k………………Pj-1        ( 4 )

( 4 )可以清楚地看到,k的位置与S(主串)无关,只与P(子串)有关。是不是很神奇呢?

4.现在要引入一个更神奇的概念,一个神奇的next数组,用来保存子串里在任何位置出现失配时,j要回退到的位置,即k值。我们称为next[k]数组。

 

我们假设已经知道next数组的值,那么我们已经可以初步完成kmp算法了。代码如下:

int KMP(char *s,char *p,int pos)

{

int i = pos;

int j = 0;

int *next = (int *)malloc(strlen(p)*sizeof(int));

assert(next != NULL);

GetNext(p,next);


while(s[i]!='\0' && p[j]!='\0')

{

if(s[i]==p[j])

{

i++;

j++;

}

       else if( j == 0)

       {

           i++;

       }

else

{

j = next[j];     // i不回退,j回退到k

}

}

 

free(next);

 

if(p[j] == '\0')

{

return i-j;

}

else

{

return -1;

}

}


5.重中之重来了,如何求next数组呢?

 

你可以很自然地想到next[0] next[1] 的值,因为当子串第0个位置失配时,j仍然等于0i++,而当子串第1个位置失配时,j要回退到第0个位置,i不变。So,,,,next[0] = 0, next[1] = 0

接下来看看其他位置失配时j要回退的位置。

 

    现在我们已知i = 0i = 1时的next值,现在想要求i=2时的next值,那么我们可以把p[2]看作新增加的数,现在我们回过头想想k的含义,k是指子串失配时j要回退的的位置,并且我们已经知道此时P0………………Pk-1  =  Pj-k………………Pj-1next[i] = k,那么倘若pk = pj,则我们可以推出next[++i]=++k

那如果pk不等于pj 呢?此时可以看作子串p0~~~pk与主串pi-k~~~pi匹配时在pk的位置失配,那么子串p0~~~pk中的下标k是不是要回退?(就像主串S串和子串P串匹配时失配后,只有子串中的j回退一样)这时我们的k要回退到哪里呢?

也许聪明的你已经发现了,我们在前面已经计算过next数组中从0~~~i的位置失配时的k值了,这里面显然包括在位置k时的next值,即next[k],所以当pk 不等于pi时,k要回退,即k=next[k]

在这里我们还要讨论一种特殊情况,即子串p0~~~pk与主串pi-k~~~pi匹配时在pk的位置失配,子串p0~~~pk中的下标k回退到0时的情况,如果此时还让k=next[0]=0,那么继续匹配,仍然不相等,仍然要回退到k=0,再继续匹配,又不相等,又回退到0~~~~~~哎呀累死我了,你发现了么?死循环了!!!

所以为了解决这种情况,我们要写一句else if (k == 0) { next[++i]=0; },这样i++了,而且也求得了next[i+1]的值,不会再出现死循环了。

void GetNext(const char*p,int *next)

{

int len = strlen(p);

next[0] = -1;

next[1] = 0;

int i = 1;

int k = 0;

while(i<len-1)

{

if(p[i]==p[k])

{

next[++i] = ++k;

}

else if(k == 0)

{

next[++i] = 0;

}

else

{

k = next[k];

}

}

}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值