next数组的含义
next[i]代表的是S[0,1,…,i-1]的前缀后缀最大值
next[0]=-1
next数组的初始化
我们使用t1来遍历所有的next的下标。if语句实现的是为next[t1+1] (长度为t1或者说下标从0到t1-1的字符串的最大前缀后缀)赋值。
赋值过程是一个递推的过程。首先需要明晰的是,当我们为长度为t1的子串配置next值时,t2的值为长度为t1-1的子串的next值,即t21所指向的位置。黄色部分为长度为t1-1的最大前缀和后缀,根据定义可知,两个黄色区域是一致的。我们比较t21处的值和t1处的值是否相等,若相等,则黄色区域可以向后拓展一个字符,以形成长度为t1的最大前缀和后缀;若不相等,则使得t22指向next[t21],此时可以观察到,图中四个绿色区域是完全一致的,继续讲t22处的值和t1处的值进行比较,重复上述过程。
void get_next(int *next,char *s2,int lens){
//用于构建s2的next数组,kmp的前奏
int t1=0,t2;
next[0]=t2=-1;
while(t1<lens){
if(t2==-1||s2[t1]==s2[t2]){
next[t1+1]=t2+1;
t1++;
t2++;
}
else t2=next[t2];
}
}
KMP求匹配串的问题
假设W为一个字符串,T为待匹配的字符串,使用t2指向W,t1指向T。
假设t1和t2所指向的位置是相同的,则t1和t2一同增加;否则,根据next数组的含义,不移动t1,仅将t2移动到nxt[t2]的位置。当t2的值出现-1的时候,代表W的第一个字符便和t1指向的字符不同,因此需要将t1和t2都向后一个,以选取新的T的起点来进行下一步的匹配。
tl = strlen(T), wl = strlen(W);
int t1 = 0, t2 = 0;
while (t1 < tl && t2 < wl) {
if (t2==-1 || T[t1] == W[t2]) {
t1++, t2++;
}
else t2 = nxt[t2]; //注意这里仅和W串有关系
if (t2 == wl) {
times++;
t2 = nxt[t2];//注意在能够进行一个完全的匹配后应该对t2进行更新
}
}
例题:poj 406
#include<iostream>
#include<string>
using namespace std;
#define WMAX 10010
#define TMAX 1000010
char W[WMAX];
char T[TMAX];
int nxt[TMAX];
int tl, wl;
int N;
int num;
void get_Lat(int* nxt, char* s, int lens) {
nxt[0] = -1;
int t1 = 0, t2 = -1;
while (t1 < lens) {
if (t2 == -1 || s[t1] == s[t2]) {
nxt[t1 + 1] = t2 + 1;
t1++;
t2++;
}
else t2 = nxt[t2];
}
}
int main() {
cin >> num;
while (num--) {
//scanf_s("\n%s\n%s", W, T);
int times = 0;
cin >> W >> T;
tl = strlen(T), wl = strlen(W);
get_Lat(nxt, W, wl);
int t1 = 0, t2 = 0;
while (t1 < tl && t2 < wl) {
if (t2==-1 || T[t1] == W[t2]) {
t1++, t2++;
}
else t2 = nxt[t2]; //注意这里仅和W串有关系
if (t2 == wl) {
times++;
t2 = nxt[t2];//注意在能够进行一个完全的匹配后应该对t2进行更新
}
}
cout << times << endl;
}
}
KMP变形 - 最小重复子串问题
最小重复子串是指对于字符串S,其若为n个s子串串接而成,则称s为s的重复子串,称长度最小的重复子串为最小重复子串。特别的,若S不能看成由子串串接而成,则其重复子串为自身,重复次数为。
例如:
absh其最小重复子串为absh,重复次数为1,ababab的最小重复子串为ab,重复次数为3
对于最小子串问题的求解思路:
以下证明该求解思路的正确性:
假设按照len-suffix[len]来对字符串进行分块(b1,b2,b3…bl),则根据最大前缀后缀的定义可以知道前缀的第i块一定等一后缀的第i块,前缀的第i块为bi,后缀的第i块为bi+1,于是便有bi=bi+1,于是可以得到所有的块都是相等的。
例题:poj 1961
#include<iostream>
#include<string>
using namespace std;
#define NMAX 1000100
char s[NMAX];
int Lat[NMAX];
int N;
void get_Lat(int* Lat, char* s2, int lens) {
//用于构建s2的Lat数组,kmp的前奏
int t1 = 0, t2;
Lat[0] = t2 = -1;
while (t1 < lens) { //逐步向前寻找t2
if (t2 == -1 || s2[t1] == s2[t2]) {
Lat[t1 + 1] = t2 + 1;
t1++;
t2++; //t2首先指向的是Lat[t1-1]即前一个的位置
}
else t2 = Lat[t2]; //保证了0 与t1-1等对的一致,若出现了不等,则会直接
}
}
int main() {
int num = 1;
while(1) {
cin >> N;
if (!N) break;
cin >> s;
get_Lat(Lat, s, N);
// for (int i = 1; i < strlen(s); i++) cout << Lat[i] << " ";
cout << "Test case #"<<num++<<endl;
for (int i = 2; i <= N; i++) {
//cout << "length"<<strlen(s) << endl;
int start = i;
int standard = start - Lat[start];
if (i % standard == 0 && i / standard>1) cout << i << " " << i / standard << endl;
}
cout << endl;
}
}