首页 > 其他分享 >KMP

KMP

时间:2023-07-07 15:27:07浏览次数:35  
标签:匹配 s2 s1 str KMP fail operatorname

烤馍片是一种算法,这种算法我忘了就学,学了就忘,所以这里再写一遍加深印象(

KMP 这个东西呢它可以在 \(O(n+m)\) 的时间复杂度内完成字符串的匹配,虽然但是我用字符串哈希我做匹配不也一个道理吗?KMP 可以与处理出来一个叫 \(fail\) 的数组(也可以叫 \(next\)),可以完成很多很厉害的东西。

下面我们先来了解一下 \(fail\) 是干嘛的:定义函数 \(\operatorname{fail}(i)\) 为字符串长度为 \(i\) 的前缀 的 最长的与字符串本身不同的 后缀 与 前缀 相同的长度。这样说好像不太容易理解也不太严谨,我们炒个例子罢,比如字符串
s: a b a b a b c
\(\operatorname{fail}(5) = 3\),什么意思嘞,就是说 a b a b a 中,前三个 a b a 和后三个 a b a 是一样的。那么对于 \(\operatorname{fail}(2)\) 呢?是 \(0\)。

\(\operatorname{fail}\) 可以这样求:首先 \(\operatorname{fail}(1) = 0\),显然易见,下面我们假设已经求出来了 \(\operatorname{fail}(1\sim i - 1)\),考虑到了 \(s_i\),我们充分利用一下之前得到的信息:

pCc3k0s.png
蓝色部分代表相同的,根据定义,左边的红色部分代表 \(str_{\operatorname{fail}(i - 1) + 1}\) 右边的红的代表 \(str_i\),如果 \(str_i = str_{\operatorname{fail}(i - 1) + 1}\)(两个红的相等),是不是就说明一点:\(fail_i = fail_{i - 1} + 1\)?

那万一它们不相同呢?那我们退而求其次,像这样
pCc3u1U.png
如果两个橙色的相等(\(str_{\operatorname{fail}(\operatorname{fail}(i - 1)) + 1} = str_{i + 1}\)),那么 \(fail_i = \operatorname{fail}(\operatorname{fail}(i - 1)) + 1\)。推广开来我们可以这样搞:对于求 \(fail_i\),定义一个指针 \(j\),\(j\) 一开始等于 \(j - 1\)。如果 \(str_{\operatorname{j} + 1} = str_i\),说明这个行,\(\operatorname{fail}(i) = \operatorname{fail}(j)+1\)。否则 \(j = \operatorname{fail}(j)\)。原因的话相信看看图就不难理解了 qwq


求出来了 \(\operatorname{fail}\),它有什么用呢?它可以搞字符串匹配。以这道题模板题 link 为例。我们先求出来了 \(s2\) 的 \(\operatorname{fail}\)。我们来手玩一个小样例

ABADBC
ABAC

首先它们登登登匹配到第三位,到了第四位发现 D 和 C 不一样,那么怎么办,从头再来?我们发现我们知道了 \(s1\) 前三位是 ABA,\(s2\) 的前三位也是 ABA,我们肯定不会这样匹配

ABA
 ABA

因为这肯定不行,聪明的我们会想到这样匹配

ABA
  ABA

深究一下其中的原因,因为这个 A 既是 ABA 的后缀,也是 ABA 的前缀……等等,\(\operatorname{fail}\)?这个相信是不难领会的,还可以举个例子

ABABAD
ABABAB

匹配到第五位失配了,但是 \(s1\) 前五位和 \(s2\) 前五位都是 ABABA,我们光来匹配这几个已知的,我们当然不会这样匹配

ABABA
 ABABA

而是

ABABA
  ABABA

因为这样肯定能匹配上一点。我们一定会从 \(\operatorname{fail}\) 开始,这相当于 \(\operatorname{fail}\) 前面都匹配过了。我们也容易理解为啥 \(\operatorname{fail}\) 要取最大——不取最大的话可能会漏。

这个算法就出来了:如果 \(i\) 是指向 \(s1\) 的,\(j\) 是指向 \(s2\) 的,前 \(i - 1\) 位与第 \(j\) 位匹配,现在我们考虑第 \(i\) 位与第 \(j + 1\) 位。如果能匹配就匹配,不能匹配那么让 \(j = \operatorname{fail}(j)\),如果 \(str_{i} = str_{j + 1}\),那么就匹配成功,否则就再让 \(j = \operatorname{fail}(j)\) 以此类推。

代码

//SIXIANG
#include <iostream> 
#include <string>
#define MAXN 1000000
#define QWQ cout << "QWQ" << endl;
using namespace std;
int fail[MAXN + 10];
void init() {
	string s1, s2;
	cin >> s1 >> s2;
	s1 = " " + s1, s2 = " " + s2;
	
	fail[1] = 0;
	for(int p = 2; p < s2.size(); p++) {
		int i = fail[p - 1];
		while(s2[i + 1] != s2[p] && i) i = fail[i];
		if(s2[i + 1] == s2[p]) fail[p] = i + 1;
		else fail[p] = i;
	}
	
	int i = 0, end = s2.size() - 1;
	for(int p = 1; p < s1.size(); p++) {
		while(s2[i + 1] != s1[p] && i) i = fail[i];
		if(s2[i + 1] == s1[p])
			i++;
		if(i == end) cout << p - end + 1 << endl;
	}
	for(int p = 1; p < s2.size(); p++)
		cout << fail[p] << ' ';
} 
int main() {
	init();
} 

可是很显然我随手搓一个字符串哈希,比这个起码要好理解很多,效率也是 \(O(n+m)\) 的呀。这里面的 \(\operatorname{fail}\) 有一个很厉害的作用。比如说有一道题根字符串的循环节有关系,我们就可以用 \(\operatorname{fail}\)。比如说这道题 link

先给出结论,答案是 \(n - \operatorname{fail}(n)\)。为啥捏?

pCc8yqJ.png

相信这张图相当明白了()

那么别的拓展也能轻而易举的得到,如果 \((n-\operatorname{fail}(n))|n\),那么这个字符串一定能由前 \(len = n - \operatorname{fail}(n)\) 个字符重复 \(n/len\) 次形成。

标签:匹配,s2,s1,str,KMP,fail,operatorname
From: https://www.cnblogs.com/thirty-two/p/17535052.html

相关文章

  • BZOJ 3942: [Usaco2015 Feb]Censoring KMP
    3942:[Usaco2015Feb]CensoringTimeLimit: 10Sec  MemoryLimit: 128MBSubmit: 476  Solved: 260[Submit][Status][Discuss]DescriptionFarmerJohnhaspurchasedasubscriptiontoGoodHooveskeepingmagazineforhiscows,sotheyhaveplentyofmateria......
  • 理解KMP算法
    KMP算法一.介绍KMP算法是一种高效的字符串匹配算法,其时间复杂度为O(n+m),其主要原因是目标串指针不回溯。1.1为什么目标串指针不用回溯?1.1.1什么是前后缀?**前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;后缀是指不包含第一个字符的所有以最后一个字符结尾......
  • 一文全解析KMP算法
    假设现在我们面临这样一个问题:有一个文本串S,和一个模式串P,现在要查找P在S中的位置,怎么查找呢?如果用暴力匹配的思路,并假设现在文本串S匹配到i位置,模式串P匹配到j位置,则有:如果当前字符匹配成功(即S[i]==P[j]),则i++,j++,继续匹配下一个字符;如果失配(即S[i]!=P[j]),令i=i-(j......
  • KMP字符串匹配
    kmp算法是优化字符串匹配效率://KMP字符串匹配://模板:#include<bits/stdc++.h>usingnamespacestd;constintN=1e6+10;chars1[N],s2[N];intn,m,ne[N];intmain(){cin>>s1+1>>s2+1;n=strlen(s1+1),m=strlen(s2+1);for(inti=2,j=0;i<=m;i++){......
  • jmu-ds-实现KMP
     jmu-ds-实现KMP #include<stdio.h>#include<string.h>constintMAX_LEN=20010;voidget_next(charstr[],intlen,intnext[]){inti=0,j=0;next[0]=-1;for(i=1;i<len;i++){while(j>0&&str[i]!=str[j])j=next[j......
  • 算法与数据结构——kmp算法
    7-1jmu-ds-实现KMP分数 10#include<stdio.h>#include<iostream>#include<string.h>usingnamespacestd;constintMAX_LEN=20010;//本题运用到字符串比对中的next[j]求法具体算法可以直接百度//get_next就是用于求next[j]的这里只需要传入目标串就行voidget_nex......
  • 关于KMP
    关于KMP平凡,而又不平凡的一天,12月31日,2022年的最后一天,让我们用几句代码迎接新年的到来。cout<<"Goodbye2022\n";printf("Hello2023!");扯正题。Kmp的简介KMP算法是字符串匹配算法,基础的用途是在文本串中快速查找与模式串相匹配的位置。一些感想我们在研究这个算法的......
  • 单模字符串匹配算法(KMP, exKMP, manacher)
    约定:本文字符串均从\(1\)开始。模式串\(T\)的长度为\(n\),匹配串\(S\)的长度为\(m\)。1.KMP1.1前缀函数给定一个长度为\(n\)的字符串\(S\),其前缀函数被定义为一个长度为\(n\)的数组\(\pi\)。其中\(\pi_i\)被定义为:若子串\(S[1\cdotsi]\)有一对相等的真前......
  • kmp 算法
    问题描述kmp算法解决的是字符串匹配问题,即:字符串P是否是字符串S的子串?如果是,它出现在s的哪些位置?这里我们称S为主串,P为模式串。思路首先是暴力匹配算法(Brute-Force算法),代码如下:voidBruteForce(strings,stringp){intlen_s=s.size(),len_p=p.size();fo......
  • kmp算法
    问题描述kmp算法解决的是字符串匹配问题,即:字符串P是否是字符串S的子串?如果是,它出现在s的哪些位置?这里我们称S为主串,P为模式串。思路首先是暴力匹配算法(Brute-Force算法),代码如下:voidBruteForce(strings,stringp){intlen_s=s.size(),len_p=p.size();fo......