Week15—KMP例题,模式串在子串中出现的次数

问题描述

ZJM 的女朋友是一个书法家,喜欢写一些好看的英文书法。有一天 ZJM 拿到了她写的纸条,纸条上的字暗示了 ZJM 的女朋友 想给 ZJM 送生日礼物。ZJM 想知道自己收到的礼物是不是就是她送的,于是想看看自己收到的礼物在纸条中出现了多少次。
【输入】
第一行输入一个整数代表数据的组数
每组数据第一行一个字符串 P 代表 ZJM 想要的礼物, 包含英语字符 {‘A’, ‘B’, ‘C’, …, ‘Z’}, 并且字符串长度满足 1 ≤ |P| ≤ 10,000 (|P| 代表字符串 P 的长度).
接下来一行一个字符串 S 代表 ZJM 女朋友的纸条, 也包含英语字符 {‘A’, ‘B’, ‘C’, …, ‘Z’}, 满足 |P| ≤ |S| ≤ 1,000,000.
【输出】
输出一行一个整数代表 P 在 S中出现的次数.
【样例输入】

3
BAPC
BAPC
AZA
AZAZAZA
VERDI
AVERDXIVYERDIAN

【样例输出】

1
3
0

问题分析

暴力匹配:枚举 i = 0, 1, 2 … , n-m,将 S[i…i+m] 与 P 作比较。如果一致,则找到了一个匹配,这种方法的复杂度为O(nm),超时!
KMP
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述做法: 使用KMP算法,暴力算法O(nm)复杂度易超时,KMP算法,在根据 Next 数组转移的过程中,如果模式串匹配到的长
度等于模式串的长度,则成功匹配,匹配成功以后根据 Next 数组进行转移,开始下一轮匹配

代码

#include<iostream>
#include<cstring>
using namespace std;
int Next[10010];
void getNext(const char ptr[],int len)
{
	Next[0]=0;
	for(int i=1,j=0;i<len;++i)
	{
		while(j&&ptr[i]!=ptr[j])
			j=Next[j-1];
		if(ptr[i]==ptr[j]) j++;
		Next[i]=j;	
	}
}

int KMP(const char str[],const char ptr[])
{
	int len1=strlen(str);
	int len2=strlen(ptr);
	int cnt =0;//cnt记录成功匹配的次数
	getNext(ptr,len2);
	for(int i=0,j=0;i<len1;++i)
	{
		while(j&&str[i]!=ptr[j])
			j=Next[j-1];
		if(str[i]==ptr[j]) j++;
		if(j==len2)
		{//匹配到模式串末尾 
			cnt++;
			j=Next[j-1];//根据Next数组转移 
		}
	} 
	return cnt; 
}
int main()
{
	int n;
	cin>>n;//组数

	for(int i=1;i<=n;i++)
	{	char str[1000010],ptr[10010];
		memset(Next,0,sizeof(Next));
		cin>>ptr;
		getchar();
		cin>>str;
		int ans=KMP(str,ptr);
		cout<<ans<<endl;
	} 
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: KMP算法可以用来高效地查找一个字符是否包含另一个字符(即子串),同时也可以用来计算子串在主出现次数。下面是使用KMP算法来求子串在主出现次数的步骤: 1. 首先,利用KMP算法求出子串的next数组,即根据子串本身的特点生成一张表格,表格每个元素记录的是子串在当前位置的最长公共前后缀的长度。 2. 接着,用求出的next数组,以及主子串的指针,循环地在主查找子串。具体做法是: a. 如果子串的字符和主的字符相等,那么继续比较子串和主的下一个字符; b. 如果子串的字符和主的字符不相等,那么根据next数组来移动子串指针,将子串向右移动一定的距离,使得下一次比较时尽可能地利用已经比较过的信息。 3. 重复以上步骤,直到子串在主出现次数全部计算完毕。 总体来说,KMP算法的时间复杂度是O(n+m),其n和m分别是主子串的长度。由于KMP算法利用了子串自身的特点,因此可以在O(n+m)的时间内完成匹配过程,比暴力匹配要快得多。 ### 回答2: KMP算法是一种字符匹配算法,其核心思想是利用已知的信息来避免无谓的比较。它主要用于在一个主查找一个模式出现的位置。 在利用KMP算法求子串在主出现次数的过程,我们需要对主子串进行如下的预处理: 1. 对于模式 pattern,我们需要计算出其前缀函数 next 数组,next[j] 表示 pattern 以 j 结尾的子串的最长公共前后缀长度; 2. 对于文本 text,我们需要在匹配的过程遍历整个,将每一位字符和模式相应位置的字符进行比较。当匹配失败时,我们利用已知 next 数组来进行以下调整:将模式右移 j - next[j] 个位置,其 j 为当前匹配失败的位置。这样可以利用已知的信息来扩大匹配范围,避免不必要的比较。 在遍历文本时,我们可以用变量 count 来记录模式在文本出现次数。当匹配成功时,则将 count 加 1。 整个算法的时间复杂度为 O(n),其 n 是文本的长度。虽然预处理需要 O(m) 的时间,但由于每次进入循环时都可以根据 next 数组来使模式右移,因此整个算法的时间复杂度是线性的。 总之,利用KMP算法求子串在主出现次数的过程,我们需要先对模式进行处理,然后进行匹配,同时根据已知信息进行调整。算法时间复杂度较低,可以快速有效地解决这一问题。 ### 回答3: KMP算法是一种字符匹配算法,可以在O(n+m)的时间复杂度内,求出一个字符在另一个字符出现次数。 要求一个子串在主出现次数,首先需要对主子串进行预处理,然后再利用KMP算法进行查找。 1. 预处理主 预处理主的目的是为了获得next数组,它记录了每个位置之前,最长的相等前缀和后缀的长度,用于KMP算法的跳转操作。 算法过程如下: 1) 初始化next[0] = -1,next[1] = 0。 2) 从第2个位置开始,依次计算出next[k]的值,其k表示计算next[k]的位置。 对于当前的k,先找到前一个位置的next[k-1],根据这个值,计算出当前位置的最长相等前缀和后缀的长度p,也就是说,前缀p个字符和后缀p个字符分别相等。然后再比较k-1位置的字符和p位置的字符是否相等。 如果相等,那么next[k] = p + 1,表示当前位置的最长相等前后缀长度为p + 1。否则,需要继续递归地向前寻找,在前一个位置的最长相等前后缀上加入k-1位置的字符,看看是否能够构成一个新的最长相等前后缀。 具体实现可参考以下代码: void getNext(char* pattern, int* next) { int m = strlen(pattern); next[0] = -1; int i = 0, j = -1; while (i < m - 1) { if (j == -1 || pattern[i] == pattern[j]) { i++; j++; next[i] = j; } else { j = next[j]; } } } 2. 利用KMP算法匹配子串 在预处理完主后,就可以利用KMP算法查找子串了。假设主为text,子串为pattern,text的长度为n,pattern的长度为m。 具体实现过程如下: 1) 初始化i和j,分别表示text和pattern的下标,从0开始。 2) 如果text[i]等于pattern[j],说明当前位置匹配成功,将i和j分别加1。如果j等于m,说明已经匹配成功一个子串,把j置为next[j],即查找下一个最长相等前后缀。 3) 如果text[i]不等于pattern[j],需要根据next数组进行跳转。假设当前匹配到了text[i]和pattern[j]不相等的位置,那么应该将j跳转到next[j]所表示的位置,然后重新进行匹配。 4) 进行上述步骤直到i等于n或者j等于m。 具体实现可参考以下代码: int kmp(char* text, char* pattern) { int n = strlen(text), m = strlen(pattern); int* next = new int[m]; getNext(pattern, next); int i = 0, j = 0, count = 0; while (i < n) { if (j == -1 || text[i] == pattern[j]) { i++; j++; if (j == m) { count++; j = next[j]; } } else { j = next[j]; } } delete[] next; return count; } 综上所述,利用KMP算法求子串在主出现次数,需要先对主进行预处理,获得next数组,然后利用KMP算法匹配子串。时间复杂度为O(n+m)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值