KMP 算法总结
初学kmp
//所以,如果文本串的长度为n,模式串的长度为m,那么匹配过程的时间复杂度为O(n),
//算上计算next的O(m)时间,KMP的整体时间复杂度为O(m + n)。
/*#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 1e5 + 10;
char s[maxn];
char p[maxn];
int next[maxn];
int main(){
printf("s = ");
scanf("%s", s);
printf("p = ");
scanf("%s", p);
for(int i = 0; p[i] != '\0'; ++ i)
next[i] = 0;
next[0] = -1;//当第一位就不同时 p 一定是往后 移动一位的, 那么就相当于i向前回溯 -1 位
next[1] = 0;//第二位不同时,代表第一位是相同的,那么i当然可以回溯 0 位, 直接在该处开始判断
//所以next 数组装的 是s的i 在 p 在j位时要向前回溯多少位
//假设为 pi 位与 对应s 不相同
int k = -1;
for(int i = 2; p[i] != '\0'; ){
if(k == -1 || p[i-1] == p[k]){//延续前面的 next
if(p[i] != p[k+1]) //k 一开始是next[i-1]
next[i] = k+1;
else
//因为不能出现p[i] = p[next[i]],这种情况必然会再次不匹配 。所以当出现时需要继续递归,k = next[k] = next[next[k]]
next[i] = next[k + 1];
i ++ , k ++;
}
else{
k = next[k];
//for(int j = max(i-next[i-1], 1); j < i; ++ j){ //从前面往后面找,先大后小,有大满足就退出了,
// //减小复杂度,如果先小后大的话,必须历遍
// //因为k属于(0, next[i-1])
// int cnt = 0, flag = 1;
// for(int k = j; k < i; ++ k){//此k历遍长度即为长度k
// if(p[k] == p[cnt])
// cnt++;
// else{
// flag = 0;
// break;
// }
// }
// if(flag){
// next[i] = i - j;
// break;
// }
//}
}
}
printf("next :");
for(int i = 0; p[i] != '\0'; ++ i)
printf("%d ",next[i]);
printf("\n");
int j = 0, flag = 0;
int plen = strlen(p), slen = strlen(s);
for(int i = 0; s[i] != '\0';){
//if(s[i] == p[j])
// i++, j++;
//else{
// i -= next[j];//多大的相同前后缀长度就 回溯 几位
// j = 0;
//}
printf("i = %d j = %d\n", i, j);
if(j == -1 || s[i] == p[j])
i ++, j ++;
else
j = next[j]; // 比前面那种更快
//si 回溯 等价于 pj 后移
if(j == plen){
printf("s[i] = %c i = %d\n", s[i], i);
flag = 1;
break;
}
}
if(flag)
printf("YES\n");
else
printf("NO\n");
}
*/
各种函数
//这类题一定要懂的单词 : prefix- 前缀 suffix- 后缀
/*#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 1e6 + 10;
int next[maxn];
char p[maxn];
char s[maxn];
int num[maxn];
//注意公认的next数组不含前导-1,是next[1] to next[plen] 这里只是个人习惯
void get_next(char *p, int *next, int plen){//next 中 nexti 位置存的是 前一位的 最大相同前后缀长度
next[0] = -1;
int k = -1;
for(int i = 0; i < plen; ){
if(k == -1 || p[i] == p[k]){
i ++;
k ++;
next[i] = k;
//if(p[i] != p[k]) //如果当前位置的字母与 回溯后相同的话,必然要继续回溯,用于优化字符串匹配函数
// next[i] = k;
//else
// next[i] = next[k];
}
else
k = next[k];
}
}
int string_seek(char *s, char *p, int slen, int plen){//从母串s中寻找子串p, 找到返回起始位置, 找不到返回-1
int pnext[plen+1];
get_next(p, pnext, plen);
int k = -1;
for(int i = -1; i < slen; ){
if(k == -1 || s[i] == p[k])
i ++, k++;
else
k = pnext[k];
if(k == plen)
return i - k;
}
return -1;
}
int string_match(char *s, char *p, int slen, int plen){//从母串s中寻找有多少 与p相同子串
int pnext[plen+1];
get_next(p, pnext, plen);
//for(int i = 0; i <= plen; ++ i)
// printf("%d ", pnext[i]);
//printf("\n");
int k = -1, i, cnt = 0;
for(i = -1; i < slen; ){
if(k == -1 || s[i] == p[k])
i ++, k++;
else
k = pnext[k];
if(k == plen){
cnt ++;
k = pnext[k];//可重叠
//k = -1;//不可重叠
}
}
return cnt;
}
int find_loop_substring(char *p, int plen){ // 寻找p串的循环节, 即多少相同子串重复构成,多种可能返回最大可能循环数
int pnext[plen+1];
get_next(p, pnext, plen);
//for(int i = 0; i <= slen; ++ i)
// printf("%d ", pnext[i]);
//printf("\n");
if(plen % (plen - pnext[plen]) == 0)
return plen / (plen - pnext[plen]);
else
return 1;//无重复子串
//如果是可以超过的,不是刚刚好,那么 plen - pnext[plen] 就是最小的子串
//所以直接return plen / (plen - pnext[plen]) + flag, flag 表示 plen % (plen - pnext[plen]) 有无余数
}
int seek_same_prefix_suffix(char *p, int plen, int x){//寻找从开头到x位置 有相同的前后缀情况,前后缀长度存入num数组中
int pnext[plen+1];
get_next(p, pnext, plen); //若x为最后一位,则x = plen
int k = x, cnt = 0;
while(1){
num[cnt ++] = k;//num数组是从大到小存的,如果要从小到大输出,就要倒序输出
k = pnext[k];
if(k == 0)
break;
}
//for(int i = cnt-1; i > 0; -- i)
// printf("%d ", num[i]);
//printf("%d\n", num[0]);
return cnt;//num数组长度
}*/
kmp 扩展到矩阵上
例题 : poj 2185
//kmp 扩展到矩阵上
//先按行 找rnext
//再按列 找cnext
//例如 aaaa
// bbbb
// cccc
// aaaa
//先按行找,循环节 为 3, 再按列找, 循环节为 1 所以是
//a
//b
//c
//最小面积为 3
//那么 abc
// def
// gcd
// abc 最小面积就是9
// aba
// aba
// aba 最小面积就是2 ab
/*#include<string>
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int r, c;
string s[10010];
int rnext[10010];
int cnext[10010][80];
int main(){
scanf("%d %d", &r, &c);
for(int i = 0; i < r; ++ i)
cin >> s[i];
rnext[0] = -1;
int k = -1;
for(int i = 0; i < r; ){
if(k == -1 || s[i] == s[k]){
i++;
k++;
rnext[i] = k;
}
else
k = rnext[k];
}
int rcnt = r-rnext[r];//这是行最小相同前后缀 也就是行循环节
//printf("%d\n", cnt);
//接下来找行循环节中每行自己的列循环节,如果其中最大的列循环节和其他行的列循环节是倍数关系,就用最大的,
//否则就用原行循环节
//printf("rcnt = %d\n", rcnt);
int ccnt[10010];
for(int i = 0; i < rcnt; ++ i){
cnext[i][0] = -1;
k = -1;
for(int j = 0; j < c; ){
if(k == -1 || s[i][j] == s[i][k]){
j ++;
k ++;
cnext[i][j] = k;
}
else
k = cnext[i][k];
}
ccnt[i] = c - cnext[i][c];//每行的列循环节
//printf("loop is ok\n");
}
sort(ccnt, ccnt + rcnt);
//for(int i = 0; i < rcnt; ++ i)
// printf("%d ", ccnt[i]);
//printf("\n");
int flag = 1;
for(int i = 0; i < rcnt; ++ i){
if(ccnt[rcnt-1] % ccnt[i] != 0){
flag = 0;
break;
}
}
//printf("flag = %d\n", flag);
if(flag)
printf("%d\n", ccnt[rcnt-1] * rcnt);
else
printf("%d\n", c * rcnt);
} */
求公共子串
例题 : poj 3080 Blue Jeans
相似 : poj 3450
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
void get_next(char *p, int *next, int plen){//next 中 nexti 位置存的是 前一位的 最大相同前后缀长度
next[0] = -1;
int k = -1;
for(int i = 0; i < plen; ){
if(k == -1 || p[i] == p[k]){
i ++;
k ++;
next[i] = k;
//if(p[i] != p[k]) //如果当前位置的字母与 回溯后相同的话,必然要继续回溯,用于优化字符串匹配函数
// next[i] = k;
//else
// next[i] = next[k];
}
else
k = next[k];
}
}
int string_seek(char *s, char *p, int slen, int plen){
int pnext[100];
get_next(p, pnext, plen);
int k = -1;
for(int i = -1; i < slen; ){
if(k == -1 || s[i] == p[k]){
i ++;
k ++;
}
else
k = pnext[k];
if(k == plen)
return i - k;
}
return -1;
}
int t, n;
char DNA[10][100];
int next[10][100];
int main(){
//while(~scanf(" %s", p)){
for(scanf("%d", &t); t; -- t){
scanf("%d", &n);
for(int i = 0; i < n; ++ i)
scanf(" %s", DNA[i]);
for(int i = 0; i < n; ++ i)
get_next(DNA[i], next[i], 60);
//可以从前到后从小到大 枚举 // 这个明显快
char ans[100];
ans[0] = '@';
int ans_cnt = 0;
for(int i = 0; i < 60; ++ i){
char temp[100];
temp[0] = DNA[0][i];
temp[1] = DNA[0][i+1];
int t_cnt = 2;
for(int j = i+2; j < 60; ++ j){
int flag = 1;
temp[t_cnt++] = DNA[0][j];
temp[t_cnt] = '\0';
for(int k = 1; k < n; ++ k)
if(string_seek(DNA[k], temp, 60, t_cnt) == -1){
flag = 0;
break;
}
if(flag == 0)
break;//这是按顺序小到大枚举的,如果前面的串不符合,后面一定不符合
else{
if(ans[0] == '@'){
for(int k = 0; k < t_cnt; ++ k)
ans[k] = temp[k];
ans[t_cnt] = '\0';
}
else{
if(strlen(temp) > strlen(ans)){//用strlen 求长度必须有'\0'
for(int k = 0; k < t_cnt; ++ k)
ans[k] = temp[k];
ans[t_cnt] = '\0';
}
else if(strlen(temp) == strlen(ans)){
int min_than = 0;
for(int k = 0; k < t_cnt; ++ k){
if(temp[k] < ans[k]){
min_than = 1;
break;
}
else if(temp[k] > ans[k]){
break;
}
}
if(min_than == 1){
for(int k = 0; k < i; ++ k)
ans[k] = temp[k];
ans[t_cnt] = '\0';
}
}
}
}
}
}
if(ans[0] != '@')
ans_cnt = 1;
//char ans[100], temp[100];
//int ans_cnt = 0;
//for(int i = 60; i > 2; -- i){//从大到小枚举 //这个慢
// for(int j = 0; j <= 60 - i; ++ j){
// int flag = 1;
// for(int k = 0; k < i; ++ k)
// temp[k] = DNA[0][k+j];
// for(int k = 1; k < n; ++ k){
// if(string_seek(DNA[k], temp, 60, i) == -1){
// flag = 0;
// break;
// }
// }
// if(flag){
// if(ans_cnt == 0)
// for(int k = 0; k < i; ++ k)
// ans[k] = temp[k];
// else{
// int min_than = 0;
// for(int k = 0; k < i; ++ k){
// if(temp[k] < ans[k]){
// min_than = 1;
// break;
// }
// else if(temp[k] > ans[k]){
// break;
// }
// }
// if(min_than == 1)
// for(int k = 0; k < i; ++ k)
// ans[k] = temp[k];
// }
// ans[i] = '\0';
// ans_cnt = i;
// }//直接存的时候比较 ,只留下一组 ,长度相同时,保留字典序小的
// }
// if(ans_cnt != 0)
// break;
//}
if(ans_cnt != 0)
printf("%s\n", ans);
else
printf("no significant commonalities\n");
}
}
//lexicographically - 字典序
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
char str[4040][220];
int next[220];
void get_next(char *p, int *next){
int plen = strlen(p);
next[0] = -1;
int k = -1;
for(int i = 0; i < plen; ){
if(k == -1 || p[i] == p[k]){
i ++;
k ++;
next[i] = k;
}
else{
k = next[k];
}
}
}
int string_seek(char *s, char *p){
int slen = strlen(s);
int plen = strlen(p);
get_next(p, next);
int k = 0;
for(int i = 0; i < slen; ){
if(k == -1 || s[i] == p[k])
i ++, k ++;
else
k = next[k];
if(k == plen)
return i - k;
}
return -1;
}
int n;
int main(){
while(~scanf("%d", &n), n){
for(int i = 0; i < n; ++ i)
scanf("%s", str[i]);
int slen = strlen(str[0]);
char ans[220], temp[220];
ans[0] = '\0';
for(int i = 0; i < slen; ++ i){
int t_cnt = 0;
for(int j = i; j < slen; ++ j){
temp[t_cnt ++] = str[0][j];
temp[t_cnt] = '\0';
int flag = 1;
for(int k = 1; k < n; ++ k){
if(string_seek(str[k], temp) == -1){
flag = 0;
break;
}
}
if(flag == 0)
break;
else{
if(strlen(temp) > strlen(ans))
for(int k = 0; k <= t_cnt; ++ k)//将'\0'也要给ans
ans[k] = temp[k];
else if(strlen(temp) == strlen(ans)){
int min_than = 0;
for(int k = 0; k < t_cnt; ++ k){
if(ans[k] > temp[k]){
min_than = 1;
break;
}
else if(ans[k] < temp[k])
break;
}
if(min_than)
for(int k = 0; k <= t_cnt; ++ k)
ans[k] = temp[k];
}
}
}
}
if(strlen(ans))
printf("%s\n", ans);
else
printf("IDENTITY LOST\n");
}
}
附上一些 kmp 题目
poj 2406 简单求周期
poj 2752 Seek the Name, Seek the Fame
poj 3461 Oulipo
poj 2185
poj 3080 Blue Jeans
poj 3450
hdu 3336 count the string
hdu 3746 Cyclic Nacklace
hdu 1358 period
hdu 2087
hdu 2594