洛谷 P3375 【模板】KMP 字符串匹配

文章目录
题目
题解
代码实现

题目

给出两个字符串 s1 和 s2,若 s1的区间 [l, r]子串与 s2 完全相同,则称 s2 在 s1 中出现了,其出现位置为 l。  
现在请你求出 s2 在 s1 中所有出现的位置。

定义一个字符串 s 的 border 为 s 的一个非 s 本身的子串 t,满足 t 既是 s 的前缀,又是 s 的后缀。  
对于 s2,你还需要求出对于其每个前缀 s' 的最长 border t' 的长度。

输入格式

第一行为一个字符串,即为 s1。  
第二行为一个字符串,即为 s2。

输出格式

首先输出若干行,每行一个整数,按从小到大的顺序输出 s2 在 s1 中出现的位置。  
最后一行输出 |s2| 个整数,第 i 个整数表示 s2 的长度为 i 的前缀的最长 border 长度。

题解

本题求两个问题:           

1是求字符串s2(称为模式串)在s1(称为主串)中出现的位置p ;

 2是求 s2的长度为 i 的前缀的最长 border 长度。   


第2个问题稍难理解,其意思是要在s2中找是不是有子串在前后都有出现,有的话最大的长度是多少 
一般的字符串匹配就是从主串S1的第一个字符和模式串S2的第一个字符开始,一个一个字符匹配。一旦失配,就从该次匹配开始时匹配的主串字符S1的下一个字符开始重新匹配。
KMP算法的关键是对于每次失配之后,都不会从头重新开始枚举,而是根据已经得知的数据,从某个特定的位置开始匹配
而要确定这个特定的位置,就需要采用一个失配数组sp来记录这个特殊位置。那么这个数组记录什么呢?
当某一轮进行模式串匹配时,发现s2的第i位失配,则失配数组sp应该记录一个位置j,
这个j<=i,且s2[j]==s2[i];并且当(j!=1)时,有s2[1]~s2[j-1]和 s2[i-j+1]~s2[i-1]的对应位依次相等。
则可以直接跳到j位置进行下一轮比较 。这里的 s2[1]~s2[j-1]就是前缀, s2[i-j+1]~s2[i-1]就是后缀 

代码实现

#include<bits/stdc++.h>
using namespace std;
char s1[1000001],s2[1000001];  
int ls1,ls2,sp[1000001],ans[1000001],j,num; //sp记录的是KMP算法中的位置j,ans记录的是最大前缀长度 
int main(){
	//为了让字符数组还是从1开始计数,输入时从s1+1开始 
    cin>>s1+1;
    cin>>s2+1;
    ls1=strlen(s1+1);
    ls2=strlen(s2+1);
    //先将失配数组确定下来,就是看当s2中第i位失配后,该跳回到哪一位
    //通过和自己匹配来得出每个点失配后要跳回的位置j  
	//注意:由于对于s2的第1位失配后,其实只能跳回到第1位(前面没有其他字符了) 
	//此外,这个失配数组只和s2有关,也就是说,如果是相同的s2,则生成的失配数组是一样的
	//sp[1]=0; 
    j=0;
    //这里可以认为是复制了一个和s2一样的串s3,i从2到ls2(s3的长度也是ls2)开始遍历
	//因为s2和s3一样的,所以也可以认为i标记的是s2的后缀,j标记的是s2的前缀
	for (int i=2;i<=ls2;++i){    
		while((j>0) && (s2[i]!=s2[j+1])){
			j=sp[j];  //i和j+1位不匹配,j要进行回退,回退到sp[j]的位置
		}
		if(s2[j+1]==s2[i]){
			j++;    //如果第i和第j+1位匹配成功,则匹配的字符个数j+1 
		}
		sp[i]=j;  //i失配后要跳回的位置j (其实就是有多少个字符匹配) 
	} 
	//开始和主串进行匹配 
    j=0;
	for (int i=1;i<=ls1;++i){    
		while((j>0) && (s1[i]!=s2[j+1])){
			j=sp[j]; //这部分其实就是在不断根据sp[j]前移j指针,直到匹配或移到头了
		}
		if(s2[j+1]==s1[i]){
			j++;    // 如果当前位匹配成功,j指针就往后移 
		}
		if(j==ls2){   //如果j和ls2一样大,其实就是完全匹配了 
			ans[++num]=i-ls2+1;   //记录下当前匹配的串在s1中的起始位置 
			j=sp[j];   //即将进行后面的串的匹配,新的j要根据sp[j]继续移动(匹配后j其实移动到sp[ls2],即首字符) 
		} 
	}
	//输出每个匹配的位置 
	for(int i=1;i<=num;++i){
		cout<<ans[i]<<endl;
	}
	//输出每个位置的最大前缀的最长 border 长度
	for(int i=1;i<=ls2;++i){
		cout<<sp[i]<<" ";
	}
	return 0;
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值