一、前缀函数知识
给定一个文本t和一个字符串 s ,我们尝试找到并展示 s 在 t中的所有出现。为了简便起见,我们用n 表示字符串 s 的长度,用m 表示文本t 的长度
这种在一个字符串中查找另一个字符串的出现位置和次数,我们可以处理成一个字符串str=s+’#’+t;就转化成了在str中找最长公共前后缀长度为s.size()的位置,并记录个数
vector<int> prefix_function(string s) {
int n = (int)s.length();
vector<int> pi(n);
for (int i = 1; i < n; i++) {
int j = pi[i - 1];
while (j > 0 && s[i] != s[j]) j = pi[j - 1];
if (s[i] == s[j]) j++;
pi[i] = j;
}
return pi;
}
string s,t,str;
cin>>s>>t;
str=s+'#'+t;
prefix_function(str);
int count=0;
int a[10000];
for(int i=0;i<str.size();++i)
{
if(pi[i]==s.size())
{
a[++count]=str[i-s.size()-1];
}
}
//所求a数组就是字符串s在字符串t中出现的下标,count是出现的次数。a数组从下标1开始
二、用Z函数实现
1、什么是Z函数?
假设我们有一个长度为n的字符串s。该字符串的Z函数为一个长度为n的数组,其中第i个元素为满足从位置i开始且为s前缀的字符串的最大长度。
换句话说,z[i] 是s和从i开始的s的后缀的最大公共前缀长度。显然z[0]就应该等于字符串的长度。
示例:
z(a,a,a,a)=[4,3,2,1]
z(a,b,b,a)=[4,0,0,1]
z(a,a,a,b,a,a,b)=[7,2,1,0,2,1,0]
首先我们可以暴力计算这个Z函数
vector<int> z_function_trivial(string s) {
int n = (int)s.length();
vector<int> z(n);
z[0]=n;
for (int i = 1; i < n; ++i)
while (i + z[i] < n && s[z[i]] == s[i + z[i]]) ++z[i];
return z;
}
显然这是很低效的一种做法,我们思考怎么利用已有的值来简化计算
Z函数
我们仍按照从i=1到len- 1的顺序计算,但是循环每个i时不再
简单把z[i]赋初值为0,而是尽可能的使用之前计算过的值。
那么考虑一下当用暴力的方法得到一个z[i]值的时候,我们还
得到了什么。
如下图
0~r-l和l~r段是匹配的
0~j和l~i是匹配的
j~r-l和i~r是匹配的
所以从i开始的字符串找最长公共前缀的话,和从j开始的字符串找到最长公共前缀长度是有关系的
vector<int> z_function(string s) {
int n = (int)s.length();
vector<int> z(n);
for (int i = 1, l = 0, r = 0; i < n; ++i)
{
if (i <= r) z[i] = min(r - i + 1, z[i - l]);//i-l相当于上图中的j
while (i + z[i] < n && s[z[i]] == s[i + z[i]]) ++z[i];
if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1;
}
return z;
}