KMP算法应用举例
Posted aininot260
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了KMP算法应用举例相关的知识,希望对你有一定的参考价值。
KMP是字符串匹配的经典算法
也是众多字符串基础的重中之重
A.
题意:给T组数据,每组有长度为n和m的母串和模式串。判断模式串是否是母串的子串,如果是输出最先匹配完成的位置,否则输出-1.
做法:直接套用模板。把char改成int。kmp函数中在模式串遍历到结尾的时候return,若没遍历到结尾,也就是不是子串返回-1
1 [cpp] view plain copy 2 #include <iostream> 3 #include <cstdio> 4 #include <cstring> 5 using namespace std; 6 int nexta[10005],a[1000005],s[10005]; 7 int n,m; 8 void getnexta(int s[]) 9 { 10 memset(nexta,0,sizeof(nexta)); 11 int k = -1,j = 0; 12 nexta[0] = -1; 13 14 while(j < n ) 15 { 16 17 if(k == -1 || s[k] == s[j]) 18 { 19 nexta[j + 1] = k + 1; 20 j ++; 21 k ++; 22 } 23 else 24 { 25 k = nexta[k]; 26 } 27 } 28 29 } 30 int kmp(int s[],int t[])//t模式串,s母串 31 { 32 getnexta(t); 33 34 int i = 0,j = 0; 35 while(i < n && j < m) 36 { 37 if(j == -1 || s[i] == t[j]) 38 { 39 i ++; 40 j ++; 41 } 42 else 43 { 44 j = nexta[j]; 45 } 46 if(j == m) 47 { 48 return i - j+ 1; 49 } 50 } 51 return -1; 52 } 53 int main() 54 { 55 // freopen("in.txt","r",stdin); 56 int T; 57 scanf("%d",&T); 58 while(T--) 59 { 60 scanf("%d%d",&n,&m); 61 for(int i = 0;i < n; i ++) 62 { 63 scanf("%d",&a[i]); 64 } 65 for(int j = 0; j < m;j ++) 66 { 67 scanf("%d",&s[j]); 68 } 69 printf("%d ",kmp(a,s)); 70 } 71 return 0; 72 }
B.
题意:给T组数据,每组有两个字符串按顺序分别为模式串和母串。判断模式串在母串中出现的次数。模式串在母串中是可以相互覆盖的。
做法:直接套用模板。在kmp中当j==m也就是模式串完全匹配时,ans++,且j = nexta[j]
1 [cpp] view plain copy 2 #include <iostream> 3 #include <cstdio> 4 #include <cstring> 5 using namespace std; 6 int nexta[1000006]; 7 char t[1000006],s[1000006]; 8 void getnexta(char s[]) 9 { 10 memset(nexta,0,sizeof(nexta)); 11 int n = strlen(s); 12 int k = -1,j = 0; 13 nexta[0] = -1; 14 while(j < n ) 15 { 16 17 if(k == -1 || s[k] == s[j]) 18 { 19 nexta[j + 1] = k + 1; 20 j ++; 21 k ++; 22 } 23 else 24 { 25 k = nexta[k]; 26 } 27 } 28 29 } 30 int kmp(char s[],char t[])//t模式串,s母串.此种为返回首次匹配的位置,不能匹配则返回-1. 31 { 32 getnexta(t); 33 int ans = 0; 34 int n = strlen(s),m = strlen(t); 35 int i = 0,j = 0; 36 while(i < n && j < m) 37 { 38 if(j == -1 || s[i] == t[j]) 39 { 40 i ++; 41 j ++; 42 } 43 else 44 { 45 j = nexta[j]; 46 } 47 if(j == m)//根据题目要求改变 48 { 49 ans ++; 50 j = nexta[j]; 51 } 52 } 53 return ans; 54 } 55 int main() 56 { 57 // freopen("in.txt","r",stdin); 58 int T; 59 scanf("%d",&T); 60 while(T--) 61 { 62 scanf("%s%s",t,s); 63 printf("%d ",kmp(s,t)); 64 } 65 return 0; 66 }
C.
题意:输入母串和模式串,以’#‘为结束。剪纸花,从母串中减去模式串问能剪出多少。这就意味着求解模式串的数量时不能重叠覆盖
做法:模板。在kmp中当j==m也就是模式串完全匹配时,ans++,且j = 0.要再次从头开始匹配
1 [cpp] view plain copy 2 #include <iostream> 3 #include <cstdio> 4 #include <cstring> 5 using namespace std; 6 int nexta[1006]; 7 char t[1006],s[1006]; 8 void getnexta(char s[]) 9 { 10 memset(nexta,0,sizeof(nexta)); 11 int n = strlen(s); 12 int k = -1,j = 0; 13 nexta[0] = -1; 14 while(j < n ) 15 { 16 17 if(k == -1 || s[k] == s[j]) 18 { 19 nexta[j + 1] = k + 1; 20 j ++; 21 k ++; 22 } 23 else 24 { 25 k = nexta[k]; 26 } 27 } 28 29 } 30 int kmp(char s[],char t[])//t模式串,s母串.此种为返回首次匹配的位置,不能匹配则返回-1. 31 { 32 getnexta(t); 33 int ans = 0; 34 int n = strlen(s),m = strlen(t); 35 int i = 0,j = 0; 36 while(i < n && j < m) 37 { 38 if(j == -1 || s[i] == t[j]) 39 { 40 i ++; 41 j ++; 42 } 43 else 44 { 45 j = nexta[j]; 46 } 47 if(j == m)//根据题目要求改变 48 { 49 ans ++; 50 j = 0; 51 } 52 } 53 return ans; 54 } 55 int main() 56 { 57 // freopen("in.txt","r",stdin); 58 while(1) 59 { 60 scanf("%s",s); 61 if(strcmp(s,"#") == 0) 62 break; 63 scanf("%s",t); 64 printf("%d ",kmp(s,t)); 65 } 66 return 0; 67 }
D.
题意:给T组数据,每组有一个字符串,只能在字符串的前面和后面增加字符,不能再中间增加,求要使这个字符串是周期循环的且周期的次数大于一,至少需要增加的字符数量。注意这个字符串是个手链,也就是说是增加字符后首位相连是周期的即可
做法:首先求最小循序节,考虑一种特殊情况就是nexta[n] = 0,这个时候前缀没有匹配后缀的地方,所以需要增加n个字符。求出最小循环节:n - nexta[n]。当n整除循环节时候,这时字符串已经是周期循环。当不整除时,最小循序节减去已经在字符串中的字符数目及ans = temp - (n % temp);(temp为最小循环节)
1 [cpp] view plain copy 2 #include <iostream> 3 #include <cstdio> 4 #include <cstring> 5 using namespace std; 6 int nexta[100005]; 7 char s[100005]; 8 void getnexta(char s[]) 9 { 10 memset(nexta,0,sizeof(nexta)); 11 int n = strlen(s); 12 int k = -1,j = 0; 13 nexta[0] = -1; 14 while(j < n ) 15 { 16 if(k == -1 || s[k] == s[j]) 17 { 18 nexta[j + 1] = k + 1; 19 j ++; 20 k ++; 21 } 22 else 23 { 24 k = nexta[k]; 25 } 26 } 27 } 28 int main() 29 { 30 // freopen("in.txt","r",stdin); 31 int T,ans,n,temp; 32 scanf("%d",&T); 33 while(T --) 34 { 35 scanf("%s",s); 36 n = strlen(s); 37 getnexta(s); 38 temp = n - nexta[n];//最小循环节 39 if(temp == n) 40 { 41 ans = n; 42 } 43 else if(n % temp == 0) 44 { 45 ans = 0; 46 } 47 else 48 { 49 ans = temp - (n % temp); 50 } 51 printf("%d ",ans); 52 } 53 return 0; 54 }
E.
题意:给字符串的长度和一个字符串。读到eof。求每个字符串中在i之前的位置是循环的且次数大于1,求这个位置i以及循环的次数
做法:求每个位置i的最小循环节,判断是否整除和个数大于1,并用除法的值求次数
1 [cpp] view plain copy 2 #include <iostream> 3 #include <cstdio> 4 #include <cstring> 5 using namespace std; 6 int nexta[1000002]; 7 char s[1000002]; 8 int n; 9 void getnexta() 10 { 11 memset(nexta,0,sizeof(nexta)); 12 int k = -1,j = 0; 13 nexta[0] = -1; 14 while(j < n ) 15 { 16 17 if(k == -1 || s[k] == s[j]) 18 { 19 nexta[j + 1] = k + 1; 20 j ++; 21 k ++; 22 } 23 else 24 { 25 k = nexta[k]; 26 } 27 } 28 29 } 30 int main() 31 { 32 // freopen("in.txt","r",stdin); 33 int t = 0,temp; 34 while(1) 35 { 36 t ++; 37 scanf("%d",&n); 38 if(n == 0) 39 break; 40 scanf("%s",s); 41 printf("Test case #%d ",t); 42 getnexta(); 43 for(int i = 1; i <= n; i ++) 44 { 45 //cout<<nexta[i]<<" "; 46 //cout<<f[i]<<endl; 47 if(nexta[i] == 0) 48 { 49 continue; 50 } 51 else 52 { 53 temp = i - nexta[i] ;//循环小节的长度 54 if((i ) % temp == 0 && (i ) / temp > 1)//这是由于nexta[i]表示的是i-1 55 printf("%d %d ",i ,(i ) / temp); 56 } 57 58 } 59 printf(" "); 60 } 61 return 0; 62 } 63 64 /* 65 a a b a a b a a b a a b 66 -1 0 1 0 1 2 3 4 5 6 7 8 67 0 1 2 3 4 5 6 7 8 9 10 11 68 */
F.
题意:每组给一个字符串,一直读到eof。这个字符串是重复写的AAAAAAA的一部分,求这个A最短是多少
做法:。。。。。。此题有问题,全网代码没有对的,我至少交了8+份代码
G.
题意:每组给以个字符串,一直读到‘.‘.字符串s = a^n,及都是由a构成的,求n的值
做法:求最小循环节,如果整除,那除得的数及为ans。如果不整除ans = 1
1 [cpp] view plain copy 2 #include <iostream> 3 #include <cstdio> 4 #include <cstring> 5 using namespace std; 6 int nexta[1000002]; 7 char s[1000002]; 8 int n; 9 void getnexta() 10 { 11 memset(nexta,0,sizeof(nexta)); 12 int k = -1,j = 0; 13 nexta[0] = -1; 14 while(j < n ) 15 { 16 17 if(k == -1 || s[k] == s[j]) 18 { 19 nexta[j + 1] = k + 1; 20 j ++; 21 k ++; 22 } 23 else 24 { 25 k = nexta[k]; 26 } 27 } 28 29 } 30 int main() 31 { 32 //freopen("in.txt","r",stdin); 33 int ans; 34 while(1) 35 { 36 ans = 0; 37 scanf("%s",s); 38 if(strcmp(s,".") == 0) 39 break; 40 n = strlen(s); 41 getnexta(); 42 if(n % (n - nexta[n]) == 0 ) 43 ans = n / (n - nexta[n]); 44 else 45 ans = 1; 46 printf("%d ",ans); 47 } 48 return 0; 49 } 50 51 52 //ababa
H.
题意:每组一个字符串,读到eof结束。寻找i使得字符串的前缀等于后缀
做法:首先n(字符串的长度)肯定是,因为此时前缀和后缀是一样的。对nexta[n]进行递归。及i= nexta[i].当nexta[i] == 0时结束。因为是nexta找到的所以以i为结束的字符串后缀等于以n为结束的字符串的后缀。可以看看kmp算法讲解中的图,体会一下
1 [cpp] view plain copy 2 #include <iostream> 3 #include <cstdio> 4 #include <cstring> 5 using namespace std; 6 int nexta[1000002]; 7 char s[1000002]; 8 int ans[1000002]; 9 int n; 10 void getnexta() 11 { 12 memset(nexta,0,sizeof(nexta)); 13 int k = -1,j = 0; 14 nexta[0] = -1; 15 while(j < n ) 16 { 17 18 if(k == -1 || s[k] == s[j]) 19 { 20 nexta[j + 1] = k + 1; 21 j ++; 22 k ++; 23 } 24 else 25 { 26 k = nexta[k]; 27 } 28 } 29 30 } 31 32 int main() 33 { 34 //freopen("in.txt","r",stdin); 35 int temp,k; 36 while(scanf("%s",s) != EOF) 37 { 38 k = 0; 39 if(strcmp(s,".") == 0) 40 break; 41 n = strlen(s); 42 getnexta(); 43 temp = n; 44 ans[k] = n; 45 k ++; 46 while(nexta[temp]!= -0) 47 { 48 temp = nexta[temp]; 49 ans[k] = temp; 50 k ++; 51 } 52 for(int i = k -1; i > 0; i --) 53 printf("%d ",ans[i]); 54 printf("%d ",ans[0]); 55 56 } 57 return 0; 58 } 59 60 61 //ababa
I.
题意:T组数据,每组m个DNA序列,每个DNA序列都有60个字符,且只由ACGT几个字母构成。判断m个DNA序列最长公共的子串是什么?如果有相同长度的公共子串,则输出字典序最小的。如果小于3输出“no ……”,大于等于3输出字符串
做法:对第一个DNA序列取从i开始到结尾的子串。与其他DNA序列进行匹配。因为是从前向后匹配,在kmp时做出改变,求出每个DNA序列和子串匹配的最长长度,再对所有最长长度取最短的那个。注意对长度相等时的处理。其中还用到strncpy,可以复制一定长度的字符串到指定字符串中。注意在最后加‘ ‘
1 [cpp] view plain copy 2 //直接枚举第一串的所有后缀,然后与后面的所有串进行比较,判断有几个字母是相同的即可 3 #include <iostream> 4 #include <cstdio> 5 #include <cstring> 6 #include <algorithm> 7 using namespace std; 8 int nexta[100]; 9 char c[12][100]; 10 char s[100]; 11 int n,m,l; 12 void getnexta() 13 { 14 memset(nexta,0,sizeof(nexta)); 15 int k = -1,j = 0; 16 nexta[0] = -1; 17 while(j < n ) 18 { 19 20 if(k == -1 || s[k] == s[j]) 21 { 22 nexta[j + 1] = k + 1; 23 j ++; 24 k ++; 25 } 26 else 27 { 28 k = nexta[k]; 29 } 30 } 31 32 } 33 int kmp() 34 { 35 int k = 0,j = -1; 36 int maxx = 100,temp = 0; 37 for(int i = 1 ;i < m; i ++) 38 { 39 temp = 0;j = 0,k = 0; 40 while(j < l && k < 60) 41 { 42 if(j == -1 || c[i][k] == s[j]) 43 { 44 j ++; 45 k ++; 46 47 } 48 49 else 50 j = nexta[j]; 51 if(j > temp)//每个DNA序列和子串匹配的最长长度 52 { 53 temp = j; 54 } 55 56 } 57 if(temp < maxx)//所有DNA序列都和子串匹配的长度 58 maxx = temp; 59 } 60 return maxx; 61 } 62 int main() 63 { 64 //freopen("in.txt","r",stdin); 65 int T,temp,num; 66 n = 60; 67 scanf("%d",&T); 68 while(T--) 69 { 70 char result[100]; 71 char t[100]; 72 num = 0; 73 scanf("%d",&m); 74 for(int i = 0; i < m; i ++) 75 { 76 scanf("%s",&c[i]); 77 } 78 for(int i = 0; i < 58; i ++) 79 { 80 l = 60 - i; 81 strcpy(s,c[0] + i); 82 getnexta(); 83 temp = kmp(); 84 if(num == temp) 85 { 86 strncpy(t,c[0] + i,temp); 87 if(t < result) 88 { 89 strcpy(result,t); 90 } 91 t[temp] = ‘