kmp算法 字符串模式匹配

串的模式匹配

最朴素的思想,暴力搜索,遍历比较每一个元素,以此判断是否相等
但是,这种太慢!!!
为此,引入kmp算法的概念,理解难度不高,多实操,应该能会

用处:

1. 普通的双字符串匹配问题

朴素的先求解nxt数组,在进行kmp匹配

模板题 洛谷P3375

#include<iostream>
#include<string>

using namespace std;
const int maxm=1e6+5;
string s,t;
int nxt[maxm];

void get_nxt(string a){
	nxt[0]=-1;
	int j=0,k=-1,len=a.size();
	while(j<len-1){
		if(k==-1||a[j]==a[k]){
			++j;++k;
			nxt[j]=k;//此处没有用优化,因为题目要求输出最长前缀后缀
		}else k=nxt[k];
	}
	return ;
}

signed main(){
	cin>>s>>t;
	get_nxt(t+".");//我学的这个nxt数组没有包含最后一位的最长前缀后缀,得自己小加一下,突然想想,发现这个好妙啊
	int i=0,j=0,lens=s.size(),lent=t.size();
	while(i<lens){
		if(j==-1||s[i]==t[j]){
			++j;++i;
		}else j=nxt[j];
		if(j==lent){
			cout<<i-lent+1<<'\n';
			j=0;
			i=i-(lent-1);
		}
	}
	for(int i=0;i<lent;++i){
		cout<<nxt[i+1]<<" \n"[i==lent-1];
	}
	return 0;
}

模板题 CF625B B. War of the Corporations

#include<iostream>
#include<string>

using namespace std;

const int maxm=30+5;
int nxt[maxm];
string s,t;

void get_nxt(string a){
    nxt[0]=-1;
    int k=-1,j=0,len=a.size();
    while(j<len-1){
        if(k==-1||a[k]==a[j]){
            ++j;++k;
            if(a[j]!=a[k]){//此处进行了优化
                nxt[j]=k;
            }else{
                nxt[j]=nxt[k];
            }
        }else k=nxt[k];
    }
    return ;
}

signed main(){
    cin>>s>>t;
    get_nxt(t);
    int i=0,j=0,lens=s.size(),lent=t.size(),ans=0;
    while(i<s.size()){
        if(j==-1||s[i]==t[j]){
            ++i;++j;
        }else{
            j=nxt[j];
        }
        if(j==lent){
            ++ans;
            j=0;//因为题意为替换,所以j=0重新开始判断
        }
    }
    cout<<ans<<'\n';
    return 0;
}
/*
上为id:199949442的代码
题为:
https://codeforces.com/contest/625/problem/B
此题为统计出现次数,所以最后的部分不是直接推出而是继续匹配
k=-1意味着当前位无需判断,继续匹配下一位即可


下为id:199943666的提交的代码
void get_nxt()
{
	int j=0,k=-1,len=strlen(a);
	nxt[0]=-1;
	while(j<len-1)
	{
		if(k==-1||a[j]==a[k])
		{
			++k;++j;
			nxt[j]=k;//此处存在赘余?得看题目
		}
		else k=nxt[k];
	}
}
int i=0,j=0;
while(i<lenb)
{
    if(j==-1||b[i]==a[j])
    {
        ++i;++j;
    }
    else
    {
        j=nxt[j];
    }
    if(j==lena)
    {
        j=0;
        cs++;
    }
}
*/

相关资料:

  1. https://www.cnblogs.com/zzuuoo666/p/9028287.html#6. 参考文献 (个人推荐,所学来源)
    https://blog.csdn.net/v_july_v/article/details/7041827#t10 (同样的文章,这个链接有些图片显示完全一点)
  2. https://www.cnblogs.com/yjiyjige/p/3263858.html

注:
资料1注意几点:
最大长度表和nxt数组的关系——不难发现,next 数组相当于“最大长度值” 整体向右移动一位,然后初始值赋为-1。
利用了nxt的优化则丢失了最大长度表!!!

2. 循环节问题

若某个字符串是由某个子串循环构成的,那么就称该子串为原串的循环节,长度最短的循环节就是最短循环节,姑且将该字符串称之为循环字符串
想想nxt数组的意义:nxt[i] 就是第 i 位之前的字符串的前缀后缀相同的最大长度是多少

  • 如果一个字符串是循环字符串,那么它的最短循环节长度为\(n-nxt[n]\),循环周期就是\(n/(n-nxt[n])\)
    相关证明
  • 如果一个字符串不是循环字符串,想求解需要添加多少字符可以将其构成循环字符串,则添加的字符数为当nxt[n]!=0时,(循环节长度-nxt[n]%循环节长度)%循环节长度;当nxt[n]==0时,为n,循环节长度即为\(n-nxt[n]\)
    (这一段的描述还有待商讨)

求最短循环字串 洛谷P4391

详见题解合集和提交代码
关键:发现\(ans=n-len_{最长前缀后缀}\)

求补最少字符构成循环 Cyclic Nacklace

题意:
给你一个字符串,你仅可以在字符串的头部和尾部添加字符,将这个字符串首尾相连,问你添加字符的最少数,使得添加后的字符串是一个循环字符串
思路:
传送门

//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long

inline ll read()
{
	ll x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
	return x*f;
}

using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
找循环节
*/
const int maxm=1e5+5,inf=0x3f3f3f3f,mod=998244353;
string ss;
int nxt[maxm];

void get_nxt(string a){
	nxt[0]=-1;
	int j=0,k=-1;
	while(j<a.size()){
		if(k==-1||a[j]==a[k]){
			++j;++k;
			nxt[j]=k;
		}else k=nxt[k];
	}
	return ;
}

void solve(){
	cin>>ss;
	get_nxt(ss);
	int n=ss.size();
	int l=n-nxt[n];
	if(nxt[n]==0) cout<<n<<'\n';//特判
	else cout<<(l-n%l)%l<<'\n';
	return ;
}

signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _=1;
	cin>>_;
	while(_--){
		solve();
	}
	return 0;
}

相关资料:

  1. 字符串最短循环节
  2. KMP算法中的循环节问题
posted on 2023-03-31 19:52  Qiansui  阅读(24)  评论(0编辑  收藏  举报