该算法由D.E.Knuth ,J.H.Morris和 V.R.Pratt提出,用于解决字符串匹配问题。
思想:
设目标串(主串)为s,模式串为t ,并设i指针和j指针分别指示目标串和模式串中正待比较的字符,设i和j的初值均为0。若有s[i]=t[j],则i和j分别加1。否则,i不变,j退回到j=next[j-1]的位置,再比较s[i]和t[j],若相等,则i和j分别加1。否则,i不变,j再次退回到j=next[j]的位置,依此类推。直到下列两种可能:
1. 模式串t中的字符全部匹配,则出现频率+1
2. 退回到j=0,将i加1,即从主串的下一个字符s[i+1]与模式串t[0]处重新开始匹配。
什么是next数组?
next数组是对模式串t中各元素位置下的一种历史信息记录。它的数学定义为:
next[i] = max{0<=k<=i|t.substring(0,k) == t.substring(i-k+1,k)}
其中:t.substring(i,k)表示t从索引i开始的k个字符的子串。
例如模式串t:bababb,其next数组为{0,0,1,2,3,1}.
如何求next数组?
求解next数组其实也是利用kmp思想。初始时next[0]=0.
对于next[i],我们将t.substring(0,i+1)作为目标串,将t.substring(0,i)作为模式串,根据q =next[i-1],只要有t[i]==t[q],则next[i] = q+1;否则q = next[q-1]继续类推,直到退回到q=0,此时表示next[i]=0.
题目
输入
第一行一个整数N,表示测试数据组数。
接下来的N*2行,每两行表示一个测试数据。在每一个测试数据中,第一行为模式串,由不超过10^4个大写字母组成,第二行为原串,由不超过10^6个大写字母组成。
其中N<=20
输出
对于每一个测试数据,按照它们在输入中出现的顺序输出一行Ans,表示模式串在原串中出现的次数。
样例输入
5
HA
HAHAHA
WQN
WQN
ADA
ADADADA
BABABB
BABABABABABABABABB
DAD
ADDAADAADDAAADAAD
样例输出
3
1
3
1
0
代码
#include<stdio.h>
#include<string.h>
#define S_LEN 1000005
#define T_LEN 10005
char t[T_LEN];
char s[S_LEN];
int next[T_LEN];
//求解next数组
void solve_next(){
int t_len = strlen(t);
memset(next, 0, t_len*sizeof(int));
int p,q;
for (p = 1; p < t_len; p++){
q = next[p - 1];
while (1){
if (t[p] == t[q]) {
next[p] = q + 1; break;
}
if (!q) break;
q = next[q - 1];
}
}
}
//查找匹配
int kmp() {
int t_len = strlen(t);
int s_len = strlen(s);
if (t_len > s_len) return 0;
int ans = 0, i = 0, j = 0;
while (j < s_len){
while (i < t_len&&j < s_len && t[i] == s[j]) {
i++; j++;
}
if (i == t_len) ans++;
if (!i) j++;
else i = next[i - 1];
}
return ans;
}
int main(void){
int N,i;
scanf("%d", &N);
for (i = 0; i < N; i++){
scanf("%s%s", t, s);
solve_next();
printf("%d\n", kmp());
}
return 0;
}
结果 | 语言 | 时间 | 内存 |
---|---|---|---|
AC | GCC | 46ms | 7MB |