理解KMP

来源:互联网 发布:js文件怎么打开 编辑:程序博客网 时间:2024/05/21 09:08

初时KMP算法

       KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度O(m+n)。


基础前提

1.前缀后缀最长公共元素长度

       最长前缀:以第一个字符开始的子串(但是不包括最后一个字符)。

       最长后缀:从最后一个字符结束的子串(但是不包括第一个字符)。

       最长公共元素就是,前缀与后缀完全匹配时,包含的最多的字符数。

    举例:字符串“ababc",其各个子串的最长公共元素长度如下表所示:

字符串

a

b

a

b

c

最大前缀后缀公共元素长度

0

0

1

2

0

     比如,对于字符串 "aba"来说,它有长度为1的相同前缀后缀 “a";而对于字符串 "abab"来说,它有长度为2的相同前缀后缀 "ab"

2.next数组

        next数组考虑的是,除当前字符外的最长相同前缀后缀,所以,可以看出其与最长公共元素长度存在关系。

        规则为:将“ababc"对应的最长公共元素长度表的数值,依次向右移动一位,然后赋初值为-1。得到下表:

字符串

a

b

a

b

c

最大前缀后缀公共元素长度

0

0

1

2

0

next数组

-1

0

0

1

2


next数组作用

       KMP的next数组,相当于告诉我们,当模式串中的某个字符跟源文本串中的某个字符匹配失败时,模式串下一步应该跳到哪个位置。如模式串中在j处的字符跟文本串在i处的字符不匹配,下一步就用,next[j]处的字符继续跟文本串i处的字符匹配(即j=next[j],相当于模式串向右移动了j-next[j]位)。图示分析如下:


如何求得next数组

       基于之前的理解,可知计算next数组最好的方法就是,采用递推形式。

       规则如下:其中k和j都是模式串中的两个下标值(k<j),已知求得了next[j]=k,如何就next[j+1]???

         1.若p[k] == p[j],则next[j+1] = next[j]+1 = k+1;

         2.若p[k] != p[j],如果此时p[next[k]] == p[j],则next[j+1] = next[k]+1;否则,继续递归前缀索引 k=next[k],而后重复此过程。

       递归求next数组,代码如下:

void GetNext(char []p, int []next){    int pLen = p.length;    next[0] = -1;    int k = -1;    int j = 0;    while(j < pLen-1){      //p[k]表示前缀,p[j]表示后缀      if(k==-1 || p[j]==p[k]){         ++k;         ++j;         next[j] = k;      }else{         k = next[k];      }    }}


KMP算法过程

       next数组是KMP的核心,上面已经得到了next数组。

       KMP的规则如下:当前源文本串S匹配到i位置,模式串P匹配到j位置

          1.如果j == -1,或者当前字符串匹配成功(即S[i] == P[j]),则令i++,j++,继续匹配下一个字符;

          2.如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令i不变,j = next[j]。意味着失配时,模式串P相对于文本串S向右移动了 j-next[j]位(即:失配字符所在位置 - 失配字符对应的next值,且此值大于等于1)。【这里是KMP算法的关键之处,以此避免了失配时,S中字符回溯产生的重复匹配过程】。

       KMP代码过程如下:

int Kmp(char []s, char[]p){    int sLen = s.length;    int pLen = p.length;    int i = 0;    int j = 0;    while(i<sLen && j<pLen){        if(j==-1 || s[i]==p[j]){//匹配成功           i++;           j++;        }else{//匹配失败           j = next[j];        }    }    if(j == pLen)        return i-j;    else        return -1;}


原创粉丝点击