34、串的模式匹配-KMP算法

一、KMP算法的思想

由D.E.Knuth、J.H.Morris和V.R.Pratt共同提出了一个改进算法,消除了Brute-Force算法中串s指针的回溯,完成串的模式匹配。时间复杂度为O(s.curlen+t.curlen),这就是Knuth-Morris-Pratt算法,简称KMP 算法。

1、KMP算法形成过程:

若S[i]≠P[j] ,为使主串指针i不回溯,可以从模式串的第k个字符开始比较,即让 j重新赋值k。k值如何确定?

若S[i]≠P[j] , j重新赋值为 k,必有:P1P2 …..Pk-1=Si -k +1 Si -k +2 ……Si -1.

同样必有:Pj -k+1Pj -k+2……Pj -1= Si -k +1 Si -k +2 ……Si -1.因此Pj -k+1Pj -k+2 ……Pj-1= P1P2 …..Pk -1.

2、KMP算法的基本思想:

        设目标串为s,模式串为t

         i、j 分别为指示s和t的指针,i、j的初值均为0。

        若有 si = tj,则i和j分别增1;否则,i不变,j退回至j=next[j]的位置 (也可理解为串s不动,模式串t向右移动到si与tnext[j]对齐 );

        比较si和tj。若相等则指针各增1;否则 j 再退回到下一个j=next[j]的位置(即模式串继续向右移动 ),再比较 si和tj。

   依次类推,直到下列两种情况之一:

   1)j退回到某个j=next[j]时有 si= tj,则指针各增1,继续匹配; 

   2)j退回至 j=-1,此时令指针各增l,即下一次比较 si+1和 t0。

3、next[j]的求法

   由定义: next[0]=-1;

   设 next[j]= k,则有P0P1P2…..Pk -1= Pj -kPj -k+2 ……Pj -1.next[j+1]= ?          

      1)若 Pk=Pj,必有P0P1P2…..Pk -1Pk= Pj -kPj -k+2 ……Pj-1Pj,因此next[j+1]=k+1=next[j]+1;

2)  若 Pk≠Pj,则P0P1P2…..Pk -1Pk≠Pj -kPj -k+2 ……Pj -1Pj.在当前匹配的过程中,已有P0P1P2…..Pk -1= Pj -kPj -k+2 ……Pj -1。若Pk≠Pj,应将模式向右滑动至以模式中的next[j]= k个字符和主串中的第j个字符相比较。若  k’=next[k],且Pk’=Pj,则说明存在一个长度为k’的子串相等:

        P0P1P2…..Pk’ -1= Pj –k’Pj –k’+2 ……Pj -1且满足: 0<k’<k<j;  Pk’= Pj 

此时有:next[j+1]=k’+1=next[k]+1

       3)否则若Pk’≠Pj:继续重复该过程。若k’=0:则令next[j+1]=0.( k’=0,next(k’)=-1).

二、、KMP算法的C语言描述

三、KMP算法的C语言实现

#include"stdio.h"

#include"stdlib.h"

#include"string.h"

#include"ctype.h"

#define OK 1

#define ERROR 0

typedef intStatus; // Status是函数的类型,其值是函数结果状态代码,如OK等

typedef intBoolean; // Boolean是布尔类型,其值是TRUE或false

#define N 16 // 数据元素个数

#define MAXKEYLEN16 // 关键字的最大长度

#defineSTACK_INIT_SIZE 10 // 存储空间初始分配量

#defineSTACKINCREMENT 2 // 存储空间分配增量

typedef struct

{

char *ch;

int length;

}HString;

voidInitString(HString &T)

 { // 初始化(产生空串)字符串T

   T.length=0;

   T.ch=NULL;

 }//InitString

intStrAssign(HString &T,char *chars)

{// 生成一个其值等于串常量chars的串T

int i,j;

if(T.ch)

       free(T.ch); //释放T原有空间

i=strlen(chars);//求chars的长度i

if(!i)

   {//chars的长度为0

    T.ch=NULL;

       T.length=0;

   }//if

else

   {

    T.ch=(char*)malloc(i*sizeof(char));//分配串空间

       if(!T.ch) exit(-1); //失败

       for(j=0;j<i;j++)

              T.ch[j]=chars[j];

       T.length=i;

   }//else

return OK;

}//StrAssign

voidStrPrint(HString &T)

{

int i;

for(i=0;i<T.length;i++)

       printf("%c",T.ch[i]);

printf("\n");

}//StrPrint

void get_next(HStringT,int next[])

 { // 求模式串T的next函数修正值并存入数组next

   int i=1,j=0;

   next[0]=-1;

   while(i<T.length)

     if(j==-1||T.ch[i]==T.ch[j])

     {

       ++i;

       ++j;

         next[i]=j;

        }//if

       else j=next[j];

 }//get_next

int Index_KMP(HStringS,HString T,int pos,int next[])

 { // 利用模式串T的next函数求T在主串S中第pos个字符之后的位置的KMP算法。

   // 其中,T非空,1≤pos≤StrLength(S)

   int i=pos,j=0;

   while(i<S.length&&j<T.length)

     if(j==-1||S.ch[i]==T.ch[j]) // 继续比较后继字符

     {

       ++i;

       ++j;

     }

     else // 模式串向右移动

       j=next[j];

   if(j>=T.length) // 匹配成功

     return i-T.length;

   else

     return 0;

 }//Index_KMP

voidOutprintS(HString &t)

{

printf("串t为: ");

StrPrint(t);

}//OutprintS

voidInputS(HString &s)

{

char ch[80];

printf("inputthe String:\n");

scanf("%s",ch);

StrAssign(s,ch);

}//InputS

int main()

{

int i,pos=1,p[N];

HString t,s;

InitString(s);//由于HSring定义了指针,所以必须初始化

InitString(t);

InputS(s);

InputS(t);

OutprintS(s);

OutprintS(t);

get_next(t,p);

i=Index_KMP(s,t,pos,p);

printf("主串和子串在第%d个字符处首次匹配\n",i+1);

return 1;

}    

六、next[j]求法的改进

Brute-Force算法的时间复杂度为O(n*m),但是实际执行近似于O(n+m)。KMP算法仅当模式与主串之间存在许多“部分匹配”的情况下,才会比B-F算法快。KMP算法的最大特点是主串的指针不需要回溯,整个匹配过程中,过主串仅需从头到尾扫描一次,对于处理从外设输入的庞大文件很有效,可以边读边匹配。

发布了321 篇原创文章 · 获赞 31 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/hopegrace/article/details/104625409