首页 > 编程语言 >KMP算法和Manacher算法

KMP算法和Manacher算法

时间:2023-12-16 10:44:06浏览次数:33  
标签:匹配 cn Manacher pos next 算法 ms KMP 回文

KMP算法

KMP算法解决的问题

KMP算法用来解决字符串匹配问题: 找到长串中短串出现的位置.

KMP算法思路

暴力比较与KMP的区别

  • 暴力匹配: 对长串的每个位,都从头开始匹配短串的所有位.
    image
  • KMP算法: 将短字符串前后相同的部分存储在\(next\)数组里,让之前匹配过的信息指导之后的匹配.
    image

\(next\)数组及其求法

\(next\)数组体现字符串某位前边子串最长匹配前缀和最长匹配后缀的匹配长度(可以有交叉).\(next[i]\)表示第\(i\)位前边的最长匹配后缀对应的最长匹配前缀的后一位.
下面是一个next数组的例子:
image

next数组的求法

\(next\)数组的求法用到了递推思想:

  1. 第\(0\)位前边没有匹配前后缀,因此\(next[0]=-1\)

  2. 第\(1\)位前缀字符串的长度为1,因此\(next[1]=0\)

  3. 站在第\(pos\)位上,就要考虑以第\(i-1\)位结尾的最长匹配(也就是不断地求\(nums[pos-1]\)与\(next[next[...next[pos-1]]]\)之间的关系):

image

  • 将\(cn\)指针指向前一个匹配后缀的下一位,即\(cn=next[pos-1]\)
  • 若前一个匹配后缀的下一位字符刚好与待匹配字符相同,即\(ms[cn]==ms[pos-1]\),则更新next数组,将\(next[pos]\)指向\(cn\)的下一位\(next[pos]=cn+1\)
  • 若前一个匹配后缀的下一位字符与待匹配字符不同,即\(ms[cn]!=ms[pos-1]\),则应该找前一个匹配数组,即\(cn=next[cn]\)
  • 若最终找到\(cn==-1\)也没找到,则\(pos-1\)位实在没有匹配后缀了,将其\(next\)数组置\(0\),即\(next[pos]=0\)
// 计算ms字符串的next[]数组
public static int[] getNextArray(char[] ms) {
	if (ms.length == 1) {
		return new int[] { -1 };
	}
	int[] next = new int[ms.length];
	next[0] = -1;	// 第0位前边没有前缀字符串,因此返回-1
	next[1] = 0;	// 第1位前缀字符串的长度为1,若该位匹配不上则只能去匹配首位,因此返回0

	// 从第2位开始,递推出后面位的next[]值
	for (int pos = 2; pos < ms.length; pos++) {
		// 一直向前找前边位的匹配前缀且该前缀的后一位与本位相同
		int cn = next[pos - 1];
		while (cn != -1 && ms[cn] != ms[pos-1]) {
			// 将前缀的后一位与当前位的前一位进行对比
			// 注意容易写错,花了两个小时排查这个玩意
			cn = next[cn];
		}

		// 判断是否能找到匹配前缀
		//if (cn != -1) {
		//	// 若能找到匹配前后缀,则返回匹配前缀的后一位
		//	next[pos] = cn + 1;
		//} else {
		//	// 若找不到匹配前后缀,返回-1
		//	next[pos] = 0;
		//}
		// 上述判断语句可以简化为一句
		next[pos] = cn + 1;
	}
	return next;
}

在写入\(next\)数组的\(pos\)位时要注意: 匹配前缀延伸的时候,要判断的是\(ms[cn]与ms[pos-1]\)之间是否相等
(因为\(next\)数组保存的是本位前一位的匹配前缀的下一位,因此\(ms[cn]\)指向匹配前缀的下一位,\(ms[pos-1]\)指向上次计算未进行匹配的位)

题目代码

P3375 【模板】KMP
#include <bits/stdc++.h>
using namespace std;
int kmp[1000005];
int la,lb,j;
char a[1000005],b[1000005];
int main() {
	cin>>a+1;
	cin>>b+1;
	la=strlen(a+1);
	lb=strlen(b+1);
	for (int i=2; i<=lb; i++) {
		while(j&&b[i]!=b[j+1])
			j=kmp[j];
		if(b[j+1]==b[i])j++;
		kmp[i]=j;
	}
	j=0;
	for(int i=1; i<=la; i++) {
		while(j>0&&b[j+1]!=a[i])
			j=kmp[j];
		if (b[j+1]==a[i])
			j++;
		if (j==lb) {
			cout<<i-lb+1<<endl;
			j=kmp[j];
		}
	}
	for (int i=1; i<=lb; i++)
		cout<<kmp[i]<<" ";
	return 0;
}

Manacher算法

Manacher算法解决的问题

Manacher算法用来寻找字符串的最长回文子串.

回文串的相关概念

  1. 回文右边界: 当前所有回文串中,回文右边界所能到达的最右位置.
  2. 回文右边界中心: 以回文右边界结尾的最长回文串的回文中心.

Manacher算法思路

从第一位开始,遍历字符串的所有位.初始时回文右边界为-1.
image
当遍历到第pos位时,有以下两种情况

  1. \(pos\)不在回文右边界\(rightBorder\)以内\((pos > rightBorder)\): 以该位为中心向两边暴力扩展回文串.
  2. \(pos\)在回文右边界\(rightBorder\)以内\((pos <= rightBorder):\)找到回文右边界中心\(rightCenter\),对应的回文左边界,以及\(pos\)关于回文右边界中心的对称点\(pos'\).这样\(pos'\)的回文半径可以用来指导\(pos\)的回文半径
    1. 若\(pos'\)回文串关于\(rightCenter\)的对称串在回文右边界以内\((pos + curRadius[pos] -1 <= rightBorder)\),说明\(pos\)的回文半径与\(pos'\)的回文半径相同
    2. 若\(pos'\)回文串关于\(rightCenter\)的对称串在回文右边界以外\((pos + curRadius[pos] -1 > rightBorder)\),由对称性可知,对称串超出回文右边界的部分一定不能构成以\(pos\)为中心的回文.因此\(pos\)的回文半径即为\(pos\)到回文右边界之间.
      在遍历过程中维护的回文右边界串并不一定是最长回文子串,如上面演示图中第一种情况为例: 遍历结束时,回文右边界串为"\(abcba\)",最长回文子串为"\(abcbadabcba\)"

题目代码

P1659 [国家集训队] 拉拉队排练

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn=22000010;
char fr[maxn],s[maxn];
ll len[maxn];
int main()
{
	scanf("%s",fr);
	ll frlen=strlen(fr),count1=0;
	s[count1++]='*';
	for(ll i=0;i<frlen;i++)
	{
		s[count1++]='#';
		s[count1++]=fr[i];
	}
	s[count1++]='#';
	s[count1++]='!';
	len[0]=0;
	ll mx=0,ans=0,id=0;
	for(ll i=1;i<count1;i++)
	{
		if(i<mx)
			len[i]=min(mx-i,len[2*id-i]);
		else
			len[i]=1;
		while(s[i-len[i]]==s[i+len[i]])
			len[i]++;
		if(len[i]+i>mx)
		{
			mx=len[i]+i;
			id=i;
			ans=max(ans,len[i]-1);
		}
	}
	printf("%lld",ans);
	return 0;
}

标签:匹配,cn,Manacher,pos,next,算法,ms,KMP,回文
From: https://www.cnblogs.com/BadBadBad/p/KMPManacher.html

相关文章

  • 算法学习笔记四一插入排序
    目录什么是插入排序算法原理示例代码什么是插入排序插入排序可理解为扑克牌摸牌的过程,手中的牌为有序序列,然后随机摸一张牌,根据牌的大小插入到有序序列对应的位置。算法时间复杂度为O(n^2)算法原理默认列表第一个元素为基准,从第二个元素和第一个元素进行比较,并放入到相应位置......
  • 【教3妹学编程-算法题】用邮票贴满网格图
    3妹:“你不是真正的快乐,你的笑只是你穿的保护色”2哥 :3妹还在唱五月天的歌啊,你不知道五月天假唱,现在全网都在骂呢。3妹:知道啊,可是关我什么事,这个歌的确好听啊。2哥 :嗯嗯,不错,还以为你是脑残粉,无论黑白都只管追星呢。3妹:我是只管追歌的,歌好听就行啦。2哥 :追哥?追哪个哥,难......
  • 代码随想录算法训练营第三天|203.移除链表元素、707.设计链表、206.反转链表
    LeetCode 203.移除链表元素题目链接:203.移除链表元素原链表删除元素(需要区分头节点和非头结点)使用虚拟头节点,统一链表操作(注意:新链表头结点是虚拟头节点的下一节点) LetCode707.设计链表题目链接:707.设计链表注意:头节点采用虚拟头节点,使得链表操作具有一致性!!!单链......
  • 算法学习笔记三一选择排序
    目录什么是选择排序算法原理示例代码什么是选择排序选择排序的主要思想是(升序为例):第一次从待排序的数据元素中选出最小的一个元素,和数组的起始位置元素进行交换,然后再从剩余的未排序元素中寻找到最小元素,然后和未排序的序列的第一个元素进行交换。每次在未排序序列中选择一个最......
  • 算法学习Day3虚拟头指针,设计链表,反转链表
    Day3虚拟头指针,设计链表,反转链表ByHQWQF2023/12/15笔记203.移除链表元素给你一个链表的头节点head和一个整数val,请你删除链表中所有满足Node.val==val的节点,并返回新的头节点。解法:虚拟头指针看起来非常简单,但是由于如果直接对原始的链表进行操作,如果头节点的v......
  • KMP & ACAM
    KMP例:在a串中查找b串的位置。(len<=1e6)O(n2)的暴力是好想的。两层循环,第一层遍历a串,第二层遍历b串,对应比较即可。但我们会发现对于a串,我们每次都不断将循环变量i右移,可匹配失败后,又将i返回至右移之前的位置。太劣了,所以我们选择取消i的返回操作,每次用b串去匹配a串。但当b失......
  • 双指针算法概念
    "双指针"是一种在数组或链表中使用两个指针来进行操作的技术。这两个指针通常被称为“快”指针和“慢”指针,或者“左”指针和“右”指针,根据其在数据结构中的移动速度或位置来命名。双指针算法在处理数组或链表的问题中非常有效,可以帮助我们以更优的时间复杂度解决问题。常见的应......
  • 代码随想录算法训练营第三天 | 链表理论基础,203.移除链表元素,707.设计链表,206.反转链
    一、链表理论基础学习:1.链表定义线性表的一种存储方式,在逻辑上连续的数据在物理存储中可以不连续。classListNode{intval;ListNodenext;ListNode(){}ListNode(intval){this.val=val;this.next=null;}ListN......
  • 代码随想录算法训练营Day3 | 203.移除链表元素、707.设计链表、206.翻转链表
    这三道题都不涉及什么难以理解的算法,是对链表基础知识的一个复习巩固对于有数据结构基础的同学来说这个没有什么难度但是,写代码的过程中,我明显感觉到,我需要更加完善和统一的代码风格,作为一个前OIer,我的c和cpp混用的情况在基础数据结构的封装层面造成了不小的混乱!我需要去补充c......
  • 代码随想录算法训练营Day2 |977.有序数组的平方、209.长度最小的子数组、59.螺旋矩阵
    明天四级考试了,时间非常紧张,好在这些数组相关的算法题很久之前就做过,思路上是不存在不理解的地方的。有序数组的平方是一道非常直观的双指针方法的应用,实现过程之中没有什么坑。长度最小的子数组就是我们的滑动窗口方法了,题目不难,但是这种处理方式有着很深刻的背景,之后还会遇到......