KMP算法详解

来源:互联网 发布:建造者模式知乎 编辑:程序博客网 时间:2024/05/23 18:40

首先明确:KMP算法就是判断字符串P是否在字符串T中出现,若出现则返回出现的位置。(可不是放电影的窝~)
最朴素的思路就是一位一位的比较,但是这样做时间效率差,最差可以达到length(P)*length(T),无法忍受(PS:这仅仅是最差的情况,实际上如果是随机数据的话,它的效率也可以忍受,甚至可能非常好)。
那么朴素算法的问题出在哪里呢?让我们举个例子。
T:ABCDEFGAIJK
P:ABCDEGG
可以发现,当P匹配到第6位时失配了(F≠G)。朴素算法很简单,把P后移一位继续比较。但是实际上,当P[1]对准T[2]时,我们早就知道一定不会匹配——在P[1]对准T[1]时是匹配的,说明T[2]肯定是B,而P[1]是A,所以不用再比较了。同理,后面的几位也不需要比较了,只有到第8位时才可能匹配上。
KMP算法对于穷举算法的优化就体现在这一点上。
可是怎么快速的知道有多少位肯定无法匹配呢?
假如我们知道应该移动x位。
根据上面的描述,我们发现,移动x位之后的第一位一定和P[1]相等(除非已无法找到匹配)。实际上,第二位、第三位、第四位……直至第length(P)位都可能相等。总结一下就是说,字符串P的一段前缀和它的一段后缀相等。说简单些,就是移动x位后可以比较位置i时,P的前i-1个位置必须满足长度为i-x-1的前缀与后缀相同。为了做到这一点,我们用next[i]来表示P的前1~i个字符中前缀与后缀的最长公共子串的长度。
下面默认字符串的下标从0开始。
首先可以想到,next[0]=next[1]=0。
于是我们知道了next[0]~next[i],要求出next[i+1]。
如果P[i]=P[next[i]],不难想到next[i+1]=next[i]+1,这一步不解释。
如果P[i]≠P[next[i]],就有些复杂了。这时候我们将长度为next[i]的字符串继续分割,得到最大公共子串长度j=next[next[i]],然后再和P[i]比较。若相等,则next[i+1]= j+1,否则就继续分下去直到j为0。
于是我们可以写出代码:

void GetNext(char* P,int* next){  int m=strlen(P);  next[0]=0;next[1]=0;  for (int i=1;i<m;i++){    int j=next[i];    while (j && P[i]!=P[j]) j=next[j];    next[i+1]= P[i]==P[j] ? j+1 : 0;  }}

next数组求好后,匹配就非常简单了。实际上,匹配的过程和求next的过程基本一样,因为next数组几乎就是“自己匹配自己”。比方说现在正在第i+1个位置出现了不匹配,我们还是要把长度为i的字符串分解,得到最大公共子串的长度next[i],然后顺着“失配边”一直走下去。代码见下:

void find(char* T,char* P,int* next){  int n=strlen(T),m=strlen(P);  GetNext(P,next);  int j=0;  for (int i=0;i<n;i++){    while (j && P[j]!=T[i]) j=next[j];    if (P[j]==T[i]) j++;    if (j==m) printf("%d\n",i-m+1);  }}

如果失配的时候一直向左走,会不会使算法变慢呢?实际上是不会的,因为最坏情况下j++了n次,故j最多可以减小n次(因为j不会是负数),即j=next[j]的次数不会超过n,所以时间复杂度为O(N)。
KMP算法的总时间复杂度为O(n+m)。n和m分别为T和P的长度。

3 0
原创粉丝点击