KMP模式匹配算法

字符串匹配算法(母串和子串)

如以下两个字符串

str1

a

a

b

a

b

b

a

str2

a

b

a

b

在字符串str1中查找str2的字符串,并返回首次查找到的下标

在还没学kmp算法之前,我一直使用的是朴素的模式匹配法

void fun(char str1[],char str2[])

 {
    int i = 0, j = 0;
    while (i < strlen(str1) && j < strlen(str2)) 
    {
        // 若相等,都前进一步
        if (str1[i] == str2[j])
         {
            i++;
            j++;
        } 
        else
         {
            i = i - j + 1;
            j = 0;
        }
    }
    // 匹配成功返回下标位置
    if (j == strlen(str2)) 
    {
        printf("%d\n",i - j);
    }
}
 

使用暴力算法固然简单易懂,但是时间复杂度不容易满足大多数的字符串匹配问题。

对于KMP算法:

KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。

首先,我们来了解什么是最长公共前后缀:

前缀和后缀集合中相同部分,同时取最长的那个。

例如: a b a b

前缀是 [ a , ab , aba ]

后缀是 [ b , ab , bab ]

所以最长公共前后缀就是 ab

我们来讨论一下next ()是怎么来的:

j

i

a

b

a

b

c

j 指向模式字符串的前缀i 指向模式字符串的后缀

str2子串

i(下标从1开始)

a

b

a

b

c

                   j

a

b

a

b

c

(1)当i=1时,next[1]=0;

(2)当i=2时,i由1到i-1就只有字符“a”,next[2]=1;

(3)当i=3时,此时由 1 到 i-1 的串为“aba”,前缀“a”与后缀"a"相等,因此可以推算出next[i]的值为1;

(4)当i=4时,此时由1到i-1的串为“abab”,前缀“ab”和后缀“ab”相等,所以next[4]=2;

(5)当i=5时,此时由1到i-1的串为“ababc”,显然后缀c没有与之配对的前缀,所以next[5]=1;

next [ j ]=

0j==1
nax(k|1<k<j且p1...p(k-1)=p(j-k-1)...p(j-1)}前缀等于后缀(最长公共前后缀)
1其他情况

求KMP算法中的 next [ ] 值:

void fun(char s[],int next[]) //s为子串,next为回溯数组
{
    int i=1,j=0;
    next[1]=0;
    while(i<strlen(s))
    {
        if(j==0||s[i]==s[j])
        {
            i++;
            j++;
            next[i]=j; //下标为i的字符前的字符串最长相等的前后缀长度为j
        }
        else j=next[j-1];//表示该处字符不匹配时应该回溯到的字符下标
    }
}

next [ ]数组的数值只与子串本身有关

next [ j ]:其值等于 第 j 位字符前面的 j - 1位字符组成的子串的前后缀重合字符数 + 1

KMP模式匹配算法的改进

面对s=aaaab这样的子串时,可以省略许多判断,由于s串的2,3,4位置的字符都和首位‘a’相等,那么可以用首位next[1]的值去取代与它相等的字符后续next[j]的值。

void fun(char s[],int next[]) //s为子串,next为回溯数组
{
    int i=1,j=0;
    next[1]=0;
    while(i<strlen(s))
    {
        if(j==0||s[i]==s[j])
        {
            i++;
            j++;
           if(s[i]!=s[j])    //若当前字符与前缀字符不同,则当前j为next在i位置上的值
              next[i]=j;
           else 
              next[i]=next[j];   //若与前缀字符相同,则将前缀字符的next值赋值给next在i位置的值
        }
        else j=next[j-1];//表示该处字符不匹配时应该回溯到的字符下标
    }
}

例题:

【模板】KMP字符串匹配

题目描述

给出两个字符串 s_1 和 s_2,若 s_1的区间 [l, r] 子串与 s_2 完全相同,则称 s_2 在 s_1 中出现了,其出现位置为 l 。

现在请你求出 s_2 在 s_1 中所有出现的位置。

定义一个字符串 s 的 border 为 s 的一个非 s 本身的子串 t,满足 t 既是 s 的前缀,又是 s 的后缀。

对于 s_2,你还需要求出对于其每个前缀 s' 的最长 border t' 的长度。

输入格式

第一行为一个字符串,即为 s_1。

第二行为一个字符串,即为 s_2。

输出格式

首先输出若干行,每行一个整数,按从小到大的顺序输出 s_2 在 s_1 中出现的位置。

最后一行输出 |s_2| 个整数,第 i 个整数表示 s_2 的长度为 i 的前缀的最长 border 长度。

样例 #1

样例输入 #1

ABABABC ABA

样例输出 #1

1 3 0 0 1

提示

样例 1 解释

对于 s_2 长度为 3 的前缀 ABA,字符串 A 既是其后缀也是其前缀,且是最长的,因此最长 border 长度为 1。

数据规模与约定

 

本题采用多测试点捆绑测试,共有 3 个子任务。

  • Subtask 1(30 points):|s_1|<=15,|s_2|<=5
  • Subtask 2(40 points):|s_1|<=10^4,|s_2|<=10^2
  • Subtask 3(30 points):无特殊约定。
  • 对于全部的测试点,保证 1<=|s_1|,|s_2|<=10^6,s_1, s_2 中均只含大写英文字母

#include<stdio.h>
#include<string.h>
char s1[10000000],s2[10000000];
int next[10000000]={0};
void fun()
{
    int i=1,j=0;
    int m=strlen(s2);
    next[0]=0;
    while(i<m)
    {
       while(j>0&&s2[j]!=s2[i])   
        j=next[j-1];
       if(s2[j]==s2[i])
        j++;
        next[i]=j;
       i++;
    }
}
int  main()
{

    int i=0,j=0;
    scanf("%s",s1);
    scanf("%s",s2);
       int n=strlen(s1);
    int m=strlen(s2);
    fun();
      while(i<n)
      {
          while(j>0&&s2[j]!=s1[i]) //两个字母不相等,则指针后退,重新匹配,j退回合适的位置
            j=next[j-1];
          if(s2[j]==s1[i])
            j++;
          if(j==m)  printf("%d\n",i+1-m+1);
          i++;
      }

   for(int i=0;i<m;i++)
      printf("%d ",next[i]);
      return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值