字符串匹配之KMP算法

一.简单的字符串匹配

我们经常需要在某一串字符中查找是否存在给定的子串,我们将给定的子串叫做模式,查找子串的问题叫做模式匹配。

进行字符串的模式匹配,最简单的方法就是依次将子串与给定的字符串依次进行比较。算法如下:

int patFind(const char* str, const char* pat){
    int pos1,pos2;
    bool isfind;
    int i=0;
    pos1=i;
    pos2=0;
    while(str[pos1]!='\0'&&pat[pos2]!='\0'){
        if(str[pos1++]!=pat[pos2++]){
            pos1=++i;
            pos2=0;
        }
    }
    if(pat[pos2]=='\0'){
        return i;
    }
    return -1;
}

 简单的匹配算法存在回溯现象,复杂度为O(m*n)

二.KMP算法的思想

在匹配的时候经常会碰到回溯,而根据对Pattern的先验计算,能避免回溯,提高匹配效率,KMP算法就是通过对Pattern进行先验计算来避免回溯的算法。

如p0…pi与ts…ts+i匹配,pi+1与ts+i+1不等,这时按照简单的匹配算法,将继续第s+1趟比较比较p0…pi-1…pm与ts+1…ts+i…ts+1+m,如果这两者匹配,那么有

p0…pi-1=ts+1…ts+i=p1…pi

这时,如果已知两者不等,那么就不用做第s+1趟比较了。同理,如果已知p0…pi-2不等于p2…pi那么就不用做第s+2趟比较。

依此类推,直到找到最大的k,使得p0…pk=pi-k…pi,这时可以直接做第s+i-k趟比较,并因为已知p0…pk=pi-k…pi=ts+i-k…ts+i了,直接比较pk+1和ts+i+1即可。

 

这时我们可以定义一个失效函数f(i),比较至pi,使得pi匹配,pi+1不匹配时,其值等于使得p0…pk=pi-k…pi且小于i的最大k值,如果找不到则值为-1.

 

对于模式pat=abaabcaba,

f(0)为-1

因为p0!=p1, f(1)为-1

因为p0=p2,f(2)为0

因为p0=p3,f(3)为0

因为p0p1=p3p4,f(4)为1

f(5)为-1

f(6)为0

f(7)为1

f(8)为2

 

如果第s趟比较在第i位失配,则下一趟直接比较pf(i-1)+1和ts+i

 

三.失效函数的计算

 已知f(j-1)=k,则有p0…pk=pj-1-k…pj-1,这时如果pk+1=pj,则f(j)=k+1,如果不等,则继续找f(k),因为p0…pf(k)=pj-1-f(k)…pj-1,这时如果pf(k)+1=pj,则f(j)=f(k)+1,依此类推。我们可以得到下面的方法来计算失效函数。

void failFunc(const char* pat, int* fail){
    fail[0]=-1;
    int len=strlen(pat);
    int k;
    for(int j=1;j<len;j++){
        k=fail[j-1];
        while(k>=0&&pat[k+1]!=pat[j]){
            k=fail[k];
        }
        if(pat[k+1]==pat[j]){
            fail[j]=k+1;
        }
        else{
            fail[j]=-1;
        }
        cout<<fail[j]<<endl;
    }
}

 

 

四.KMP模式匹配算法的实现

 

int kmpFind(const char* str, const char* pat){
    int pos1=0,pos2=0;
    int patlen=strlen(pat);
    int* fail=new int[patlen];
    failFunc(pat,fail);
    while(str[pos1]!='\0'&&pat[pos2]!='\0'){
        if(str[pos1]==pat[pos2]){
            pos1++;
            pos2++;
        }
        else if(pos2==0){
            pos1++;
        }
        else{
            pos2=fail[pos2-1]+1;
        }
    }
    if(pat[pos2]=='\0'){
        return pos1-patlen;
    }
    return -1;
}

虽然计算失效函数需要浪费一定的时间和空间,可是从代码我们可以看到KMP没有任何的回溯,时间复杂度仅为(m+n),当模式比较长时KMP算法比起简单的模式匹配有显著的优势。

 

运行结果:

int _tmain(int argc, _TCHAR* argv[])
{
    char str[] = "ababaabaabaabcabaaaabbcccc";
    char pat[] = "abaabcaba";
    
    cout<<str<<endl;
    cout<<pat<<" can be found at position:"<<kmpFind(str,pat)<<endl;
    
    return 0;
}

 

    原文作者:Jingle Guo
    原文地址: https://www.cnblogs.com/studynote/p/3383004.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞