首页 > 其他分享 >KMP 学习笔记

KMP 学习笔记

时间:2023-12-13 13:47:52浏览次数:29  
标签:space ll 笔记 学习 cdots KMP 字符串 pi border

符号规定

先来规定一些符号。

  1. \(\lvert S\rvert\) 代表这个字符串 \(S\) 的长度。
  2. \(S_{l\cdots r}\) 代表字符串从第 \(l\) 个字符到第 \(r\) 个字符组成的字串。
  3. \(F(S,i)\) 等同于 \(S_{1\cdots i}\)(就是字符串长度为 \(i\) 的前缀)
  4. \(E(S,i)\) 等同于 \(S_{\lvert S\rvert-i+1\cdots \lvert S\rvert}\) (就是字符串长度为 \(i\) 的后缀)注意在我们的定义里这个后缀是从左往右读的
  5. \(B(S)\) 表示 \(S\) 的一个最长 border 的长度(具体什么是 border 之后再谈)

前置芝士—border

定义

如果一个字符串 \(S\) 存在一个长度为 \(x\) 的 border,则有 \(F(S,x)=E(S,x)\)。也就是一个字符串的长度为 \(x\) 的前缀与长度为 \(x\) 的后缀相等。

例子

对于这个字符串:

\[\Large{qwertyqwertyqwerty} \]

它的border有:

\[\textcolor{orange}{qwerty}qwertyqwerty \]

\[qwertyqwerty\textcolor{orange}{qwerty} \]

\[\textcolor{orange}{qwertyqwerty}qwerty \]

\[qwerty\textcolor{orange}{qwertyqwerty} \]

特别的,我们为了方便一般不认为一个完整的字符串是 border。

求法

对于一个字符串 \(S\),我们一般会记录最大 border。我们只要能求出来最大 border 就可以求出所有的 border。这是因为border 是存在包含关系的。就比如上一个例子的第二个 border 实际上是基于第一个 border 的。

那我们考虑求法。设 \(\pi_i\) 代表 \(B(F(S,i))\),即 \(S\) 的长度为 \(i\) 的前缀的最长 border。

KMP 发现 \(\pi\) 是可以被递推的。

我们目前假设知道了 \(\pi_{1\cdots i}\),现在要求 \(\pi_{i+1}\)。

有一个结论:\(\pi_{i+1}\) 一定是 \(\pi_{1\cdots i}\) 中的一个 \(+1\)。因为它只有前面是 border 了之后才能拼上。

那么我们不妨设一个 \(f(x,c)\) 代表目前 \(F(S,x)\) 是一个 border 的前缀,然后我们考虑它所属的 border 能否匹配上 \(c\) 这个字符。

我们先给出 \(f\) 的递归逻辑,之后再解释。

\[f(x,c)= \left\{ \begin{array}{l} x+1 \space\space\space(S_{x+1}=c)\\ 0\space\space\space\space\space\space\space\space\space\space(x=0)\\ f(\pi_x,c)\\ \end{array} \right. \]

\[\pi_i=f(\pi_{i-1},S_i) \]

首先解释最简单的 \(0\),这是因为如果当前能匹配的已经没有了,然后上面那个能够匹配的东西又不符合,所以就没有更小的原来的 border 用来匹配了。所以就返回 \(0\)。

然后的话我们先来从一开始看一下一幅图:

这就是我们的初始状态。因为 border 的性质两个绿色部分是完全一样的,所以我们一开始判断的就是黄色是否等于蓝色,如果是的话显然这就是一个新的 border,然后因为 \(\pi_{i-1}\) 就是之前最长的了,所以显然满足 \(\pi_i\) 性质,直接更新。

否则的话我们根据递归就是判断下面这幅图:

这个时候很神奇的事情就发生了,根据 border 性质,四个紫色部分显然是一样的,那么还是判断黄色和蓝色的就行了。因为一定有一个紫色在开头,还有一个紧挨着蓝色。然后紫色也一定是最长的次大,所以在绿色不满足性质的情况下它仍然是满足 \(\pi\) 的性质的。然后就愉快的求完了。

KMP

KMP 算法是一种用 \(O(\lvert S\rvert)\) 的时间复杂度来求出模式串 \(T\) 在文本串 \(S\) 中的所有出现位置的算法。

算法流程

我们先对于 \(T\) 求出 \(\pi\),也就是知道了所有的 \(B(F(T,i))\)。
然后开始匹配。我们首先枚举 \(S_i\),并记录 \(l\) 满足 \(T_{1\cdots l}=S_{i-l+1\cdots i}\)。显然如果 \(l=\lvert T\rvert\) 则 \(S_{i-l+1\cdots i}=T\),也就是匹配成功一次。那么关键在于我们怎么线性维护这个 \(l\)。

先说结论:直接让 \(l=f(l,S_i)\) 即可。

对于这样一幅图,你会发现它就是答案。首先合法性肯定可以理解,因为每一个相同颜色的部分根据 border 性质显然是一样的,不过多解释。至于最优性,你会考虑深蓝色部分为什么不可以再延伸,这是因为如果可以再向左延伸,又因为 \(S\) 需要包含前面的,则 border 也会变得更长,不符合 \(f\) 的定义,矛盾。所以直接这么求即可。

例题

P3375 【模板】KMP

题目大意

给出两个字符串 \(s_1\) 和 \(s_2\),若 \(s_1\) 的区间 \([l, r]\) 子串与 \(s_2\) 完全相同,则称 \(s_2\) 在 \(s_1\) 中出现了,其出现位置为 \(l\)。
现在请你求出 \(s_2\) 在 \(s_1\) 中所有出现的位置。
\(1 \leq |s_1|,|s_2| \leq 10^6\),\(s_1, s_2\) 中均只含大写英文字母。

解法

kmp 模版,参考上方解法。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MAXN=1e6+5;
string s,t;
ll n,m;
ll pi[MAXN];
ll find_next(ll ed,char need) {
	if(t[ed+1]==need) {
		return ed+1;
	}
	return ed==0?0:find_next(pi[ed],need);
}
void kmp() {
	for (int i=2;i<=m;++i) {
		pi[i]=find_next(pi[i-1],t[i]);
	}
}
void find() {
	ll j=0;
	for (int i=1;i<=n;++i) {
		j=find_next(j,s[i]);
		if(j==m) {
			cout<<i-m+1<<endl;
			j=pi[j];
		}
	}
}
int main() {
	cin>>s>>t;
	n=s.size(),m=t.size();
	s=" "+s;
	t=" "+t;
	kmp();
	find();
	for (int i=1;i<=m;++i) {
		cout<<pi[i]<<" ";
	}
	return 0;
}

标签:space,ll,笔记,学习,cdots,KMP,字符串,pi,border
From: https://www.cnblogs.com/tanghg/p/kmp-note.html

相关文章

  • SQLServer数据库JDBC连接串参数的简单学习
    SQLServer数据库JDBC连接串参数的简单学习背景前段时间一直跟同事一起处理SQLServer比其他数据库的deadlock更多的问题.涉及到了几个驱动的参数.想着问题基本上告一段落,将这一块的情况总结一下.便于后续遇到问题时的快速处理.关于参数现阶段的字符连接串为:jdbc:s......
  • linux命令find、locate、ll-i显示内容命令学习
    find路径匹配表达式-namefilename:查找指定名称的文件-userusename:查找指定用户的文件-groupgrpname:查找属于指定组的文件-print:显示查找结果-sizen:查找大小为n块的文件,+n表示查找大小大于n块的文件,-n表示查找大小小于n块的文件;nc表示查找大小为n个字符的文件root@localhos......
  • JVM虚拟机系统性学习-类加载子系统
    类加载子系统JVM架构如下图,接下来将从类加载子系统、运行时数据区来逐步讲解JVM虚拟机类加载的时机类加载的时机主要有4个:遇到new、getstatic、putstatic、invokestatic这四条字节码指令时,如果对应的类没有初始化,则要先进行初始化new关键字创建对象时读取或设置一个类型的......
  • Rong晔大佬教程学习(0):前言
    2023-12-13在安装了tinyriscv的工具链之后,本想着说去看那个技术文档,但是那个技术文档只是相当于一个“使用手册”,而不是技术教程,所以说还是得去补一补计组的知识。前几天买了本riscv的书,想配合着b站的计组教程刷一刷,但是几天了书还在路上,万幸的是在b站看到了Rong晔......
  • MarkDown学习
    标题二级标题三级标题字体加粗斜体斜体加粗废弃引用这是一个引用分割线图片超链接这是一个超链接列表ss5577556677表格ABCqwqq3q......
  • STM32学习随笔 12.13
    慢摸摸的学习之前跟着B站江协科技UP学51感觉没啥,学到STM32就感觉很吃力,又想钻研清楚,看到定时器TIM章节零零总总差不多耽搁快进一个月了总结下近期学到的东西学习掌握多元条件运算符,这样可以省略很多if()else()或者switch()case;语句示例:      i-=(i>10000)?10......
  • 机器学习-搜索技术:从技术发展到应用实战的全面指南
    在本文中,我们全面探讨了人工智能中搜索技术的发展,从基础算法如DFS和BFS,到高级搜索技术如CSP和优化问题的解决方案,进而探索了机器学习与搜索的融合,最后展望了未来的趋势和挑战,提供了对AI搜索技术深刻的理解和展望。关注TechLead,分享AI全维度知识。作者拥有10+年互联网服务架构......
  • 通过PowerShellPlus示例脚本学习PowerShell之-输出SQLServer服务属性
    ##=====================================================================##Title:Get-MSSQL-ServerAttrib-Csv##Description:ConnecttoSQLServerandoutputserverattributestoCSV##Author:Idera##Date:1/28/2009##Input......
  • Python学习多线程、多进程、多协程记录
    一、多线程应用于请求和IO#1.Python中关于使用多线程多进程的库/模块#2.选择并发编程方式(多线程Thread、多进程Process、多协程Coroutine)前置知识: 一、三种有各自的应用场景 1.一个进程中可以启动多个线程 2.一个线程中可以启动多个协程 二、各自优缺点 1......
  • 阅读笔记:《代码大全》阅读笔记十
    《代码大全》是我在软件开发领域的一本必读书籍。这本书几乎涵盖了软件开发的方方面面,从编码到设计、测试到调试等各个环节都有详细的讲解和指导。首先,我被作者对于代码的重视所深深吸引。他在书中强调,代码质量决定了软件的可靠性和可维护性。好的代码应该易读、易懂、易维护。通......