kmp

本文围绕KMP算法展开,介绍其虽古老但经典,还提及2016GDOI中的相关题目。对比普通字符串匹配,阐述KMP算法更快的原因,通过实例说明其思路,讲解了KMP数组的计算方式及定义,指出核心思想是next数组,并说明了其在匹配相同子串中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

kmp算法虽然是一个非常老的算法了,各界OI比赛最近也很少碰过。但是,作为一个很经典的算法,且在2016GDOI中专门出了kmp模板题(当时还是个蒟蒻,暴力只拿了30分……)。所以,今天又打了一遍kmp且写了题解,既是帮大家理解kmp,也是对我自己知识的一个梳理。

首先,我们要了解相较于普通字符串匹配快在哪。普通匹配是一位一位往前推,再一次次从选定的开头往后对比,时间复杂度近似于O(N^2)。而kmp快就快在它不用一位一位的选定开头,而是动态以最优的开头作比较,比朴素算法效率提高了不少。看不懂这段话的人也不用着急,往后看就对了。

首先,让我们来谈谈此算法的思路。针对于每一位,kmp算法已经预处理出了一个对应kmp数组的单元,映射着如果此位失配,它可能的最靠后的一个重新开头是哪一个。让我们举一个例子:假如让aaab与aab匹配。一开始,aab的aa与aaab的开始的aa成功匹配,但到了第三位失配了。此时,朴素算法会跳出,找到下一个开头进行比对。然而kmp算法用kmp数组得知,这位失配后应该可能却可以与第二位匹配成功,而又成功,于是又继续往后匹配,然后就匹配成功了,只比较了5次,比O(n^2)好了不少。

而kmp函数又是怎么算出来的呢?其实,只要自己与自己匹配即可。

kmp[i]的基本定义是:在第1-第i-1位中前缀与后缀相同的部分最长是多长。这样,即可以理解为,若第i位失配了,则至少要往前跳多少步,才可能重新匹配得上。这样便可以解释前面的例子了。

 

 

 

 

 

KMP算法的核心思想是next数组。

接下来,我来谈谈我对KMP数组的理解。

KMP算法是用来匹配有多少相同字串的一种算法。

1、next数组记录前缀与后缀相等的位置,然后跳到这。

2、数组即记录后缀与前缀相等的个数

如ABCABC

那么next数组就是

/*
hiho 1015 
题意:s1在s2中出现了多少次 
*/
#include<cstdio>
#include<cstring>
using namespace std;
const int N1=1e4+50;
const int N2=1e6+10;
char s1[N1];
char s2[N2];
int next[N1];
int main()
{
	int n;
	scanf("%d",&n);
	while(n--){
		scanf("%s%s",s1,s2);
		int i=0;
		int j=-1;
		next[0]=-1;
		int len=strlen(s1);
		while(i<len){
			if(j==-1||s1[i]==s1[j]) next[++i]=++j;
			else j=next[j];
		}
		i=j=0;
		int sum=len;
		len=strlen(s2);
		int ans=0;
		while(i<len){
			if(j==-1||s1[j]==s2[i]){
				i++;
				j++;
			}
			else j=next[j];
			if(j==sum) ans++;
		} 
		printf("%d\n",ans);
	}
	return 0;
}
/*
洛谷 p3375
题意 s1是子串 
输出: 
若干行,每行包含一个整数,表示s2在s1中出现的位置
接下来1行,包括length(s2)个整数,表示前缀数组next[i]的值。 
*/ 
#include<bits/stdc++.h>
using namespace std;
const int N1=1e6+50;
char s1[N1];
char s2[N1];
int _next[N1];
int main()
{
    scanf("%s%s",s2,s1);
    int i=0;
    int j=-1;
    _next[0]=-1;
    int len1=strlen(s1);
    while(i<len1){
        if(j==-1||s1[i]==s1[j]) _next[++i]=++j;
        else j=_next[j];
    }
    i=j=0;
    int sum=len1;
    int len=strlen(s2);
    //int ans=0;
    while(i<len){
        if(j==-1||s1[j]==s2[i]){
            i++;
            j++;
        }
        else j=_next[j];
        if(j==sum){
            //ans++; 这个可以求s1在s2中有多少个 
            printf("%d\n",i-len1+1);
        }
    } 
    for(int i=1;i<=strlen(s1);i++) printf("%d ",_next[i]);
    printf("\n");
    return 0;
}

 

我们这里说的KMP不是拿来放电影的(虽然我很喜欢这个软件),而是一种算法。KMP算法是拿来处理字符串匹配的。换句话说,给你两个字符串,你需要回答,B串是否是A串的子串(A串是否包含B串)。比如,字符串A="I'm matrix67",字符串B="matrix",我们就说B是A的子串。你可以委婉地问你的MM:“假如你要向你喜欢的人表白的话,我的名字是你的告白语中的子串吗?” 解决这类问题,通常我们的方法是枚举从A串的什么位置起开始与B匹配,然后验证是否匹配。假如A串长度为n,B串长度为m,那么这种方法的复杂度是O (mn)的。虽然很多时候复杂度达不到mn(验证时只看头一两个字母就发现不匹配了),但我们有许多“最坏情况”,比如,A= "aaaaaaaaaaaaaaaaaaaaaaaaaab",B="aaaaaaaab"。我们将介绍的是一种最坏情况下O(n)的算法(这里假设 m<=n),即传说中的KMP算法。 之所以叫做KMP,是因为这个算法是由Knuth、Morris、Pratt三个提出来的,取了这三个人的名字的头一个字母。这时,或许你突然明白了AVL 树为什么叫AVL,或者Bellman-Ford为什么中间是一杠不是一个点。有时一个东西有七八个人研究过,那怎么命名呢?通常这个东西干脆就不用人名字命名了,免得发生争议,比如“3x+1问题”。扯远了。 个人认为KMP是最没有必要讲的东西,因为这个东西网上能找到很多资料。但网上的讲法基本上都涉及到“移动(shift)”、“Next函数”等概念,这非常容易产生误解(至少一年半前我看这些资料学习KMP时就没搞清楚)。在这里,我换一种方法来解释KMP算法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值