kmp算法及模板

在介绍KMP算法之前,先介绍一下BF算法。
一.BF算法

BF算法是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串P的第一个字符进行匹配,若相等,则继续比较S的第二个字符和P的第二个字符;若不相等,则比较S的第二个字符和P的第一个字符,依次比较下去,直到得出最后的匹配结果。

举例说明:

S:  ababcababa

P:  ababa

BF算法匹配的步骤如下

       i=0                                   i=1                             i=2                         i=3                          i=4

第一趟:ababcababa 第二趟:ababcababa 第三趟:ababcababa 第四趟:ababcababa 第五趟:ababcababa

         ababa                            ababa                          ababa                        ababa                       ababa

        j=0                                   j=1                            j=2                         j=3                         j=4(i和j回溯)



          i=1                                 i=2                           i=3                            i=4                        i=3

第六趟:ababcababa 第七趟:ababcababa 第八趟:ababcababa 第九趟:ababcababa 第十趟:ababcababa

          ababa                              ababa                           ababa                        ababa                        ababa

         j=0                                  j=0                           j=1                           j=2(i和j回溯)            j=0



          i=4                                    i=5                          i=6                           i=7                          i=8

第十一趟:ababcababa 第十二趟:ababcababa 第十三趟:ababcababa 第十四趟:ababcababa 第十五趟:ababcababa

                 ababa                               ababa                           ababa                          ababa                          ababa

           j=0                                    j=0                         j=1                            j=2                         j=3



                i=9

第十六趟:ababcababa

                   ababa

                j=4(匹配成功)
代码实现
int BFMatch(char *s,char *p)
{
    int i,j;
    i=0;
    while(i<strlen(s))
    {
        j=0;
        while(s[i]==p[j]&&j<strlen(p))
        {
            i++;
            j++;
        }
        if(j==strlen(p))
            return i-strlen(p);
        i=i-j+1;                //指针i回溯
    }
    return -1;    
}

其实在上面的匹配过程中,有很多比较是多余的。在第五趟匹配失败的时候,在第六趟,i可以保持不变,j值为2。因为在前面匹配的过程中,对于串S,已知s0s1s2s3=p0p1p2p3,又因为p0!=p1!,所以第六趟的匹配是多余的。又由于p0p2,p1p3,所以第七趟和第八趟的匹配也是多余的。在KMP算法中就省略了这些多余的匹配。
二.KMP算法

KMP算法之所以叫做KMP算法是因为这个算法是由三个人共同提出来的,就取三个人名字的首字母作为该算法的名字。其实KMP算法与BF算法的区别就在于KMP算法巧妙的消除了指针i的回溯问题,只需确定下次匹配j的位置即可,使得问题的复杂度由O(mn)下降到O(m+n)。

在KMP算法中,为了确定在匹配不成功时,下次匹配时j的位置,引入了next[]数组,next[j]的值表示P[0…j-1]中最长后缀的长度等于相同字符序列的前缀。

对于next[]数组的定义如下:

1) next[j] = -1 j = 0

2) next[j] = max(k): 0<k<j P[0…k-1]=P[j-k,j-1]

3) next[j] = 0 其他

如:

P a b a b a

j 0 1 2 3 4

next -1 0 0 1 2

即next[j]=k>0时,表示P[0…k-1]=P[j-k,j-1]

因此KMP算法的思想就是:在匹配过程称,若发生不匹配的情况,如果next[j]>=0,则目标串的指针i不变,将模式串的指针j移动到next[j]的位置继续进行匹配;若next[j]=-1,则将i右移1位,并将j置0,继续进行比较。

int  KMPMatch(char *s,char *p)
{
    int next[100];
    int i,j;
    i=0;
    j=0;
    getNext(p,next);
    while(i<strlen(s))
    {
        if(j==-1||s[i]==p[j])
        {
            i++;
            j++;
        }
        else
        {
            j=next[j];       //消除了指针i的回溯
        }
        if(j==strlen(p))
            return i-strlen(p);
    }
    return -1;
}

因此KMP算法的关键在于求算next[]数组的值,即求算模式串每个位置处的最长后缀与前缀相同的长度, 而求算next[]数组的值有两种思路,第一种思路是用递推的思想去求算,还有一种就是直接去求解。

1.按照递推的思想:

根据定义next[0]=-1,假设next[j]=k, 即P[0…k-1]==P[j-k,j-1]

1)若P[j]==P[k],则有P[0…k]==P[j-k,j],很显然,next[j+1]=next[j]+1=k+1;

2)若P[j]!=P[k],则可以把其看做模式匹配的问题,即匹配失败的时候,k值如何移动,显然k=next[k]。

因此可以这样去实现:

void getNext(char *p,int *next)
{
    int j,k;
    next[0]=-1;
    j=0;
    k=-1;
    while(j<strlen(p)-1)
    {
        if(k==-1||p[j]==p[k])    //匹配的情况下,p[j]==p[k]
        {
            j++;
            k++;
            next[j]=k;
        }
        else                   //p[j]!=p[k]
            k=next[k];
    }
}

2.直接求解方法

void getNext(char *p,int *next)
{
    int i,j,temp;
    for(i=0;i<strlen(p);i++)
    {
        if(i==0)
        {
            next[i]=-1;     //next[0]=-1
        }
        else if(i==1) 
        {
            next[i]=0;      //next[1]=0
        }
        else
        {
            temp=i-1;
            for(j=temp;j>0;j--)
            {
                if(equals(p,i,j))
                {
                    next[i]=j;   //找到最大的k值
                    break;
                }
            }
            if(j==0)
                next[i]=0;
        }
    }
}

bool equals(char *p,int i,int j)     //判断p[0...j-1]与p[i-j...i-1]是否相等  
{
    int k=0;
    int s=i-j;
    for(;k<=j-1&&s<=i-1;k++,s++)
    {
        if(p[k]!=p[s])
            return false;
    }
    return true;
}

模板一
此模板是判断模板串在主串中出现的次数。如果输出第一次出现在主串中的位置即(i-m+1).

#include <iostream>
#include <algorithm>
#include <cmath>
#include <ctype.h>
#include <cstring>
#include <cstdio>
#include <sstream>
#include <cstdlib>
#include <iomanip>
#include <string>
#include <queue>
#include <map>

using namespace std;
const int maxn=1e6+10;
int Next[maxn];
char str[maxn],mo[maxn];//str是主串,mo是模串
int t,n,m;

void getNext()//递推next数组
{
    int i=0,j=-1,len=strlen(mo);
    while(i<len)
    {
        if(j==-1||mo[i]==mo[j])
            Next[++i]=++j;
        else
        {
            j=Next[j];
        }
    }
}
int kmp()
{
    int i=0,j=0,l1=strlen(str),l2=strlen(mo);
    int ans=0;
    while(i<l1)
    {
        if(j==-1||mo[j]==str[i])
            i++,j++;
        else
            j=Next[j];
        if(j==l2)
            ans++;
    }
    return ans;
}

int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%s%s",mo,str);
        Next[0]=-1;
        getNext();
        printf("%d\n",kmp());
    }
    return 0;
}

模板二

//求str对应的next数组
void getNext(char const* str, int len)
{
    int i = 0;
    next[i] = -1;
    int j = -1;
    while( i < len )
    {
        if( j == -1 || str[i] == str[j] )   //循环的if部分
        {
            ++i;
            ++j;
            //修正的地方就发生下面这4行
            if( str[i] != str[j] ) //++i,++j之后,再次判断ptrn[i]与ptrn[j]的关系
                next[i] = j;      //之前的错误解法就在于整个判断只有这一句。
            else
                next[i] = next[j];  //这里其实是优化了后的,也可以仍是next[i]=j
            //当str[i]==str[j]时,如果str[i]匹配失败,那么换成str[j]肯定也匹配失败,
            //所以不是令next[i]=j,而是next[i] = next[j],跳过了第j个字符,
            //即省去了不必要的比较
            //非优化前的next[i]表示前i个字符中前缀与后缀相同的最大长度
        }
        else                                 //循环的else部分
            j = next[j];
    }
}

//在目标字符串target中,字符str出现的个数
//n为target字符串的长度,m为str字符串的长度
int kmp_match(char *target,int n,char *str,int m){
    int i=0,j=0;  //i为target中字符的下标,j为str中字符的下标
    int cnt=0;   //统计str字符串在target字符串中出现的次数
    while(i<=n-1){
        if(j<0||target[i]==str[j]){
            i++;
            j++;
        }
        else{
            j=next[j]; //当j=0的时候,next[0]=-1,这样j就会小于0,所以一开始有判断j是否小于0
        }

        //str在target中找到匹配
        if(j==m){
            cnt++;
            j=next[j];
        }
    }
    return cnt;
}
//在目标字符串target中,若存在str字符串,返回匹配成功的第一个字符的位置
int kmp_search(char *target,int n,char *str,int m){
    int i=0,j=0;  //i为target中字符的下标,j为str中字符的下标
    int cnt=0;   //统计str字符串在target字符串中出现的次数
    while(i<n && j<m){
        if(j<0||target[i]==str[j]){
            i++;
            j++;
        }
        else{
            j=next[j]; //当j=0的时候,next[0]=-1,这样j就会小于0,所以一开始有判断j是否小于0
        }
    }
    if(j>=m)
        return i-m;
    else
        return -1;
}

模板例题:
HDU—1711

题意:模板串在主串中第一次出现的位置

#include <iostream>
#include <algorithm>
#include <cmath>
#include <ctype.h>
#include <cstring>
#include <cstdio>
#include <sstream>
#include <cstdlib>
#include <iomanip>
#include <string>
#include <queue>
#include <map>

using namespace std;
const int maxn=1e6+10;
int Next[maxn];
int str[maxn],mo[maxn];//str是主串,mo是模串
int t,n,m;

void getNext()//递推next数组
{
    int i=0,j=-1;//len=strlen(mo);
    while(i<m)
    {
        if(j==-1||mo[i]==mo[j])
            Next[++i]=++j;
        else
        {
            j=Next[j];
        }
    }
}
int kmp()
{
    int i=0,j=0;//l1=strlen(str),l2=strlen(mo);
    //int ans=0;
    while(i<n)
    {
        if(j==-1||mo[j]==str[i])
            i++,j++;
        else
            j=Next[j];
        if(j==m)
            return i-m+1;
    }
    return -1;
    //return ans;
}

int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        for(int i=0;i<n;i++)
            scanf("%d",&str[i]);
        for(int i=0;i<m;i++)
            scanf("%d",&mo[i]);
        //scanf("%s%s",mo,str);
        Next[0]=-1;
        getNext();
        printf("%d\n",kmp());
    }
    return 0;
}

HDU—1686

题意:求模板串在主串中出现的次数。

#include <iostream>
#include <algorithm>
#include <cmath>
#include <ctype.h>
#include <cstring>
#include <cstdio>
#include <sstream>
#include <cstdlib>
#include <iomanip>
#include <string>
#include <queue>
#include <map>

using namespace std;
const int maxn=1e6+10;
int Next[maxn];
char str[maxn],mo[maxn];//str是主串,mo是模串
int t,n,m;

void getNext()//递推next数组
{
    int i=0,j=-1,len=strlen(mo);
    while(i<len)
    {
        if(j==-1||mo[i]==mo[j])
            Next[++i]=++j;
        else
        {
            j=Next[j];
        }
    }
}
int kmp()
{
    int i=0,j=0,l1=strlen(str),l2=strlen(mo);
    int ans=0;
    while(i<l1)
    {
        if(j==-1||mo[j]==str[i])
            i++,j++;
        else
            j=Next[j];
        if(j==l2)
            ans++;
    }
    return ans;
}

int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%s%s",mo,str);
        Next[0]=-1;
        getNext();
        printf("%d\n",kmp());
    }
    return 0;
}

最小循环节模板

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值