1、朴素KMP
先是朴素版本的KMP,求解大部分串匹配还有循环节等问题。
/*
s1:子串
s2:母串
nxt[0]:-1
nxt[i]:s1到第i-1位的最大匹配长度,i=1,2,...,len
eg:
s1: a b c a a b c d b a b c a
nxt:-1 0 0 0 1 1 2 0 0 0 1 2 3 4
kmp返回字串在母串中出现的第一个位置,没有就是-1
*/
const int maxn = 200005;
void get_next(char s[], int nxt[], int len) {
int i = 0,j = -1;
nxt[0] = -1;
while(i < len) {
if(j == -1 || s[i] == s[j]) {
++i;
++j;
nxt[i] = j;
} else {
j = nxt[j];
}
}
}
int kmp(char s1[], char s2[], int len1, int len2) {
get_next(s1, nxt, len1);
int i = 0, j = 0;
while(i < len2 && j < len1) {
if(j == -1 || s2[i] == s1[j]) {
++i;
++j;
if (j == len1) {
//可重叠 j=next[j]
//不可重叠 j=0
return i - len1 + 1;//返回第一个符合的位置
}
} else {
j = nxt[j];
}
}
return -1;
}
2、优化KMP
朴素版本的KMP如果碰到了循环串,get_next()函数的常数将变大,因为循环节会导致get_next()函数的while部分中"j=nxt[j]"每次只能向前跳一个循环节的长度。
最极端的情况是aaaaaaaaaa...,能被某些题目卡成n^2。这类题目的特点是前面的部分全是循环节,循环节后面的字母可能会有变动,比如下面的例题。
显然,如果后面的循环节不匹配,前面的循环节也不会匹配,所以最好能够一次跳过所有循环节,避免重复的比较。利用next_val数组来实现。
next_val[i]:第i位发生失配时字串下标j应该跳转到的位置。
next_val的计算也很简单:
next_val[0] = -1;
for (int i = 1; i < len; ++i) {
if (s[i] == s[nxt[i]]) next_val[i] = next_val[nxt[i]];
else next_val[i] = nxt[i];
}
在get_next()函数求nxt数组的同时求next_val数组,那么前面的next_val值就可以利用到后面的计算中,大大加快了KMP的速度。
/*
s1:子串
s2:母串
nxt[0]:-1
nxt[i]:s1到第i-1位的最大匹配长度,i=1,2,...,len
next_val[i]:优化后的跳转位置
eg:
s1: a b c a a b c d b a b c a
nxt:-1 0 0 0 1 1 2 0 0 0 1 2 3 4
val:-1 0 0 -1 1 0 0 0 0 -1 0 0 -1
kmp返回字串在母串中出现的第一个位置,没有就是-1
优化后的KMP使用next_val作为跳转表,消除了循环节的影响。
*/
void get_next(char s[], int nxt[], int val[], int len) {
int i = 0,j = -1;
nxt[0] = -1;
val[0] = -1;
while(i < len) {
if(j == -1 || s[i] == s[j]) {
// 计算当前位val值
if (j == -1) {
val[i] = (s[i] == s[0] ? -1 : nxt[i]);
} else {
val[i] = (s[i] == s[nxt[i]] ? val[nxt[i]] : nxt[i]);
}
// 当前位的nxt值,存在后一位中
++i;
++j;
nxt[i] = j;
} else {
j = val[j];
}
}
}
int kmp(char s1[], char s2[], int len1, int len2) {
get_next(s1, nxt, nxt_val, len1);
int i = 0, j = 0;
while(i < len2 && j < len1) {
if(j == -1 || s2[i] == s1[j]) {
++i;
++j;
if (j == len1) {
//可重叠 j=next[j]
//不可重叠 j=0
return i - len1 + 1;//返回第一个符合的位置
}
} else {
//j = nxt[j];
j = nxt_val[j]; // KMP中的跳转也改成next_val
}
}
return -1;
}
例题:牛客网暑期ACM多校训练营(第九场)F.Typing practice