一、acwing
整体思路:
我们不想要像暴力那样,一个位置不匹配就往下一位继续匹配
我们可以找到某个位置让模板串的前部分都相同,我们只用去匹配下一个位置是否相同即可
观察可发现,ABCD四个位置都是相同的
找到相同部分AC就等于找到相同部分BD所以,我们的问题就转化为找到模板串他的前后缀公共数量的值。所以我们需要做一个预处理,求ne数组,得到每个位置的前后缀公共数量值了,然后在匹配的时候,通过ne数组我们可以快速得到A的长度j,从而方便我们去匹配i和j+1的位置。
为什么要找这个呢,因为我们想要节省一点时间,所以我们想要找到模板串从开头开始某个j长度的部分和我失配位置前的j个长度是相同的,那么我们就不用再去比较前前面的了,只用继续往下匹配就好。
如果想要匹配,模板串开头开始j的长度的部分一定i前部分一定要匹配得上我们才会开始匹配第i个位置,不然根本不会轮到i位置比较,因为前面都失配了。
1.模板
题目:
模板:
#include <iostream>
using namespace std;
const int N = 1000010, M = 1000010;
int n, m, ne[N];
char s[N], p[N];
int main(){
cin >> n >> p + 1 >> m >> s + 1;
for (int i = 2, j = 0; i <= n ; i ++ ){
while(j && p[i] != p[j + 1]) j = ne[j];
if (p[i] == p[j + 1]) j ++;
ne[i] = j;
}
for (int i = 1, j = 0; i <= m ; i ++ ){
while(j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j ++;
if (j == n){
printf("%d ",i-n);
j =ne[j];
}
}
return 0;
}
2.模板理解
#include <iostream>
using namespace std;
const int N = 1000010, M = 1000010;
int n, m, ne[N];
char s[N], p[N];
//这里s为长串,也就是被匹配的那一个
//p为短串,也就是匹配的那一个
int main(){
cin >> n >> p + 1 >> m >> s + 1;
//这里是求next数组
//next数组的下标对应字符串的位置(这里的字符串我们是从1开始算位置的)
//而数组里面的值对应的是每个位置最大的前后缀公共数量的值
//比如abcab,ne[2]对应的是b,b前缀是a,后缀是b,没有公共部分,所以ne[2]=0,
//而ne[4]对应的是a,他有一个前缀为a,但是再往后ab和ca不对应,所以ne[4]只能是1,不能是2
//这里为什么要从2开始呢
//因为第一个字符他一定没有前缀,所以根本不用匹配,所以我们从第二个开始
//这里的j是我们前、后缀的长度
for (int i = 2, j = 0; i <= n ; i ++ ){
while(j && p[i] != p[j + 1]) j = ne[j];
//我们匹配的位置是i和j+1,所以这里的ne[j]当中的j的意义是说,我们j+1不匹配,
//我们的j就要回到上一个位置的ne的值,所以等号左边的j是长度的意思,右边的j是指位置。
if (p[i] == p[j + 1]) j ++;
//如果匹配的话,我们就继续增加前后缀长度,看看下一个位置是否能继续相等
ne[i] = j;
}
//这里开始真正匹配
for (int i = 1, j = 0; i <= m ; i ++ ){
while(j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j ++;
if (j == n){//j的长度为匹配的p的长度,说明我们已经找到了一个和模板串匹配的位置
printf("%d ",i-n);//i为我们匹配结束的位置,不是开始匹配的位置,所以一定要记得减去长度才是我们开始匹配到字符串的位置
j = ne[j];
}
}
return 0;
}
二、DS串应用--KMP算法
题目描述
学习KMP算法,给出主串和模式串,求模式串在主串的位置
算法框架如下,仅供参考
输入
第一个输入t,表示有t个实例
第二行输入第1个实例的主串,第三行输入第1个实例的模式串
以此类推
输出
第一行输出第1个实例的模式串的next值
第二行输出第1个实例的匹配位置,位置从1开始计算,如果匹配成功输出位置,匹配失败输出0
以此类推
代码:
#include<iostream>
#include<cstring>
using namespace std;
string s;
string p;
int ne[1010];
int main()
{
int t;
cin >> t;
while (t--)
{
cin >> s >> p;
int slen = s.length();
int plen = p.length();
s = "0" + s;;
p = "0" + p;
//j是最大相同前后缀和
//为什么从2开始呢,因为第一个字符没有前缀所以我们可以直接跳过
for (int i = 2,j=0; i <= plen; i++)
{
while (j && p[i] != p[j+1])
//就是我们希望找到某一段的j和当前部分i是匹配的,所以我们需要计算前后缀和,
//
{
j = ne[j];
//这里其实就是!!!!j+1的前一个!,所以是j!!!
}
if (p[i] == p[j+1])
j++;
ne[i] = j;
}
cout << "-1 ";
for (int i =1;i<plen; i++)
{
cout << ne[i] << " ";
}
cout << endl;
int flag = 0;
for (int i =2, j = 0; i <= slen; i++)
{
while (j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j++;
//就是刚好匹配到一个完整的子串
if (j ==plen)
{
cout << i-j+1<<endl;
flag = 1;
break;
}
}
if (flag == 0) cout << "0" << endl;
memset(ne, 0, sizeof ne);
}
return 0;
}