KMP算法

来源:互联网 发布:淘宝确认收货 花呗 编辑:程序博客网 时间:2024/06/18 04:43

如果我们有两个字符串,分别是S和P,要找出P在S中的位置,该怎么查找呢?
这里写图片描述
这种匹配方式成为暴力匹配,BF算法,当 S[i]==P[j] 匹配成功,i++,j++

代码如下:

#include<stdio.h>#include<assert.h>#include<string.h>int BF(const char *str,char *sub){    assert(str!=NULL && sub!=NULL)    int slen=strlen(str);    int plen=strlen(sub);    int i;    int j;    while(i<slen && j<plen)    {        if(str[i]==sub[j])        {            i++;            j++;        }        else        {              j=0;              i=i-j+1;        }}if(j>=plen){    return i-j;}else{    return -1;}}

它的时间复杂度为O(strlen(P)*strlen(S)),这样它的时间复杂度就很大,那么如何缩小它的时间复杂度呢?
就是KMP算法的核心,利用已匹配相同的字符,不让i 回溯,j不停地往后走,让时间复杂度变成O(strlen(p)+strlen(S))。
例如这样两个字符串:

S: BBC ABCDAB ABCDABCDABDE
P: ABCDABD
按照我们之前的暴力匹配来看一下:
这里写图片描述

当s[i]和p[j]不相等时,i继续往后走,j=0,这是我们之前暴力匹配算法的思路。
A和D 不相等,那么i就要回退到4, j=0,显而易见的是s[i]和p[j]是不匹配的,i又回退到5,j=0,肯定也是不匹配的,那么我们是不是发现了我们绕远了路呢?那么能不能跳过重复的比较匹配过程呢?

这俩个字符串哥们就开始商量,我们怎么样才可以了如执掌呢?
s[i]说 :刚才都是我在找你,跳来跳去还找不到你真是辛苦!
p[j]说:哎呀,我的小心肝,别怕,你别跳了,我来找你,我有一套秘诀,简称KMP算法,马上就能找到你。

KMP算法:利用已经有部分匹配的字符串信息,保证让 i 值不回退,通过修改 j 的值,让p[j] 移动到有效的位置进行匹配。

那么我们再来看看这两个字符串,我们的理想效果是这样的:
这里写图片描述
额,我们数一下下标的位置:s[9]和p[6]匹配失败时,如果我们让 i 不回退,j的值往后走,那么它应该跳到如上图所示的p[2] 位置,然后 p[2] 再与 s[9]进行匹配,这是不是就完美了。

可我们如何解决让p每次都跳到有效的位置呢?
如果每个数组中的每个值都有一个规定跳到有效位置的K值就好了,那么我们就直接可以用了。
我们假设有这样的一个K值,还是用刚才的例子,来推一推:
这里写图片描述

这样我们就能找到k的值,所以当p[j]不匹配时,就跳到相对应的k的位置,我们放在数组里next[j]=k。
但我们注意的是:虽然我们找到了K值,也就是不匹配字符p[j]之前重复匹配的字符,但我们一定要找到的是匹配字符的最大长度,而这个最大长度才是next数组存在的意义。如何找到最大长度呢?
1、子串在失配前找两个相等的最长真子串(不包含自己),一个以0下标作为开头,另一个以失配前的字符为结尾。
例如:
ABCDABD 求它每个字符存在的最大相同字符串
A B C D A B D
next[j] -1 0 0 0 0 1 2

再看一个例子: ABCDABCDABDE
A B C D A B C D A B D E
next[j] -1 0 0 0 0 1 2 3 4 5 6 0

例3: a b a b a b a b a a a b b b a b c d a b
next[j] -1 0 0 1 2 3 4 5 6 7 1 1 2 0 0 1 2 0 0 1

我们知道了next[j]了,可是我们能不能求出next[j+1]的值呢?这样就知道next[j]数组的所有值,就能直接用这个数组来找到合适的移动位置。

我们一起找一下规律吧,用一下上面的一个例子:
0 1 2 3 4 5 6 7 8 9
a b a b a b a b a a a b b b a b c d a b
next[j] -1 0 0 1 2 3 4 5 6 7 1 1 2 0 0 1 2 0 0 1
观察next数组,我们知道next[0]=-1,next[1]=0,是不变的。

也就是说next[j]=k,如何求next[j+1]?
next[j]=k,是通过表达式 p0………pk-1=pj-k……..pj-1 求得的 ,
if pk=pj

      两式相加 :p0 ......pk=pj-k.....pj-1 pj                       k+1=k+1                       next[j+1]=k+1                 验证一下:next[4]=2    那么next[5]=?                  k=2   j=4                  p[2]==p[4]==a   那么next[5]=3,查上面已经得出的下标5的next值为3                      if       pk!=pj  呢?               例如我们要知道next[9]=7,求 next[10]?                 j=9   k=7                 1. p[j] !=p[k]     那只能接着往下找,直到pk==pj                 next[7]=5       k=5                2.p[j]!=p[k]                 next[5]=3      k=3                3.  p[j]!=p[k]                 next[3]=1      k=1                4.p[j]!=p[k]                 next[1]=0      k=0                5.p[j]==p[k]     next[10]=0+1=1

哎呀,5次查找之后,终于找到了 。
代码实现:

 #include<stdio.h> #include<assert.h>  #include<stdlib.h>  #include<string.h>static void Getmaxnext(const char *sub,int *next){     int len=strlen(sub);     next[0]=-1;     next[1]=0;     int j=1;     int k=0;     while(j+1<len)    {         if(k==-1 || sub[j]==sub[k])         {            next[++j]=++k;         }         else         {            k=next[k];         }    }}int KMP(const char *str,const char *sub,int pos){    if(pos<0)    {        return -1;    }    int strlen=strlen(str);    int sublen=strlen(sub);    int i=pos;    int j=0 ;    int *next=(int *)malloc(sizeof(int)*sublen);    Getmaxnext(sub,next);    while(i<strlen && j<sublen)    {         if(j==-1 && str[i]==sub[j])         {             i++;             j++;         }          else         {             j=next[j];         }       }    free(next);    if(j>=sublen)    {         return i-j;    }    else    {         return -1;    }}

KMP优化:
刚才我们算next[9]=7,求 next[10]?
j=9 k=7
1. p[j] !=p[k] 那只能接着往下找,直到pk==pj
next[7]=5 k=5
2.p[j]!=p[k]
next[5]=3 k=3
3. p[j]!=p[k]
next[3]=1 k=1
4.p[j]!=p[k]
next[1]=0 k=0
5.p[j]==p[k] next[10]=0+1=1

经过了5次查找,前4次由于p[j]!=p[k],所以一直在查找,p[9]=a, p[7]=a;而接下来的p[k]==p[7],那怎么又能找到呢?所以就多走了几步,如何省掉这些多走的路呢?
            0 1 2 3 4 5 6 7 8 9
            a b a b a b a b a a a b b b a b c d a b
next[j] -1 0 0 1 2 3 4 5 6 7 1 1 2 0 0 1 2 0 0 1
还是通过next[9] 求next[10]?
nextval:-1 0 -10 -10 -1 0 -1-1-1 000 -1 0 2 0-10

让每次跳转到的字符如果相同的话,就设成一样的值。
p[9]的nextval=-1,k=0, p[0]==p[9]==a;
next[10]=0+1=1
是不是快很多了呢?
代码实现,改动next 数组求法:

 #include<stdio.h> #include<assert.h>  #include<stdlib.h>  #include<string.h>static void Getmaxnext(const char *sub,int *next){     int len=strlen(sub);     next[0]=-1;     next[1]=0;     int j=1;     int k=0;     while(j+1<len)    {         if(k==-1 || sub[j]==sub[k])         {             if(sub[j]!=sub[k])           {                 next[++j]=++k;          }            else           {                 next[j]=next[k];          }     }         else         {            k=next[k];         }    }}

还有两个简单的例子再理解一下:
这里写图片描述

原创粉丝点击