KMP的初步认识及题目分析

Posted knightero

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了KMP的初步认识及题目分析相关的知识,希望对你有一定的参考价值。

KMP算法对于像我这样的ACM菜鸡来说实在难以理解

虽然有大佬讲课,但是还是不理解

感谢下面这位大犇的博客,让我看懂了KMP(跪谢)

https://www.cnblogs.com/SYCstudio/p/7194315.html

---------------------------------------------------------------------------------------------------------------------------

下面是我对于学校题目的一些总结

KMP模板(解释太麻烦了,看上面的大佬博客就行)

 1 #include <iostream>
 2 #include <stdio.h>
 3 #include <string>
 4 using namespace std;
 5 int tnext[1000010];
 6 
 7 void get_next(string ptr)
 8 {
 9     tnext[0] = -1;
10     int len = ptr.size();
11     int j;
12     for (int i = 1; i <len; i++)
13     {
14         int j = tnext[i - 1];
15         while (j>=0&&ptr[i] != ptr[j+1])
16         {
17             j = tnext[j];
18         }
19         if (ptr[i] == ptr[j+1]) j++;
20         tnext[i] = j;
21        // cout << tnext[i] << endl;
22     }
23 
24 }
25 void KMP(string str, string ptr)
26 {
27     get_next(ptr);
28     for (int i = 0, j = -1; i < str.size(); i++)
29     {
30         while (j >= 0 && (j == str.size() - 1 || str[i] != ptr[j+1]))
31         {
32             j = tnext[j];
33         }
34         if (str[i] == ptr[j + 1]) j++;
35         if (j == ptr.size() - 1) cout << i - j << endl;
36     }
37 
38 }
39 int main()
40 {
41     string str, ptr;//str是长串,ptr是短串即目标串,要在str中去找ptr
42     cin >> str >> ptr;
43     KMP(str,ptr);
44     return 0;
45 }

---------------------------------------------------------------------------------------------------------------------------

HDU-4763-Theme Section

http://acm.hdu.edu.cn/showproblem.php?pid=4763

本题大意就是要求找出一串字符串的border,且该border在中间出现过

思路是先算next数组,如果next数组在全长的情况下不是-1,那么就进一步去字符串里找border

 1 #include <iostream>
 2 #include <stdio.h>
 3 #include <string>
 4 #include <cstring>
 5 using namespace std;
 6 int tnext[1000010];
 7 
 8 void get_next(string ptr)
 9 {
10     tnext[0] = -1;
11     int len = ptr.size();
12     int j;
13     for (int i = 1; i < len; i++)
14     {
15         int j = tnext[i - 1];
16         while (j >= 0 && ptr[i] != ptr[j + 1])
17         {
18             j = tnext[j];
19         }
20         if (ptr[i] == ptr[j + 1]) j++;
21         tnext[i] = j;
22         // cout << tnext[i] << endl;
23     }
24 
25 }
26 bool cc;
27 void KMP(string str, string ptr)
28 {
29     get_next(ptr);
30     for (int i = 0, j = -1; i < str.size(); i++)
31     {
32         while (j >= 0 && (j == str.size() - 1 || str[i] != ptr[j + 1]))
33         {
34             j = tnext[j];
35         }
36         if (str[i] == ptr[j + 1]) j++;
37         if (j == ptr.size() - 1)
38         {
39             if (i - j != 0 && i - j != str.size() - ptr.size() + 1) cc = true;
40         }
41     }
42 
43 }
44 int main()
45 {
46     int n;
47     cin >> n;
48     while (n--)
49     {
50         string str;
51         cin >> str;
52         cc = false;
53         memset(tnext, 0, sizeof(tnext));
54         get_next(str);
55         int k = tnext[str.size() - 1];
56         while (2 * (k + 1) + 1 > str.size()) k = tnext[k];//前缀和后缀不能接壤
57         if (k == -1) cout << "0" << endl;
58         else
59         {
60             while (k != -1)//先是从满足条件的最大border开始判断,不行的话用小一点的border,直到没有
61             {
62                 string ptr(str, 0, k + 1);//截取border
63 
64                 KMP(str, ptr);
65                 if (cc) {//找到了满足中间出现border条件的
66                     cout << k + 1 << endl; break;
67                 }
68                 else k = tnext[k];//没找到就看下一层border
69             }
70             if(!cc) cout << 0 << endl;
71         }
72     }
73     return 0;
74 
75 }

---------------------------------------------------------------------------------------------------------------------------

HDU-3336-Count the string

 http://acm.hdu.edu.cn/showproblem.php?pid=3336

题目大意就是找出一个字符串的所有前缀在字符串里出现过几次

思路是:由于每个前缀自身都会出现一次,如果一个字符串长度为n,那么总共有n个前缀,所以前缀至少出现n次

之后任务就变成了前缀在其之后出现了几次

而next数组的含义是字符串长度为k时,其border的长度,如果我们遍历next数组求和,就可以达到有多少个前缀出现在了后面

 1 #include <iostream>
 2 #include <cstring>
 3 #include<string>
 4 using namespace std;
 5 int pos[1000100];
 6 int num = 0;
 7 void get_next(string ptr)
 8 {
 9     pos[0] = -1;
10     
11     for (long long int i = 1,j=-1; i < ptr.size(); i++)
12     {
13         
14         while (j >= 0 && ptr[i] != ptr[j + 1]) j = pos[j];
15         if (ptr[i] == ptr[j + 1]) j++;
16         pos[i] = j;
17        
18     }
19 
20 }
21 
22 int main()
23 {
24     ios::sync_with_stdio(false);
25     cin.tie(0);
26     cout.tie(0);
27     long long int t;
28     cin >> t;
29     while (t--)
30     {
31         long long int size;
32         string str;
33         cin >> size;
34         cin >> str;
35         num = 0;
36         get_next(str);
37         for (int i = 1; i < size; i++)
38         {
39             if (pos[i] != -1) num++;
40             num %= 10007;
41         }
42        
43        cout << (num+size)%10007 << endl;
44     }
45 }

---------------------------------------------------------------------------------------------------------------------------

HDU-1358-Period

http://acm.hdu.edu.cn/showproblem.php?pid=1358

题目大意:有一串字符串,找出他的长度为多少时,是由循环节组成的

解题思路:遍历总长度,算出每个长度对应的next数组,那么周期就是len-next[k],如果长度%周期==0

说明它是由整数个周期组成的,输出长度/周期即可

 1 #include <iostream>
 2 #include<string>
 3 using namespace std;
 4 int pos[1000100];
 5 void get_pos(string str)
 6 {
 7     pos[0] = -1;
 8     for (int i = 1; i < str.size(); i++)
 9     {
10         int j = pos[i - 1];
11         while (j >= 0 && str[i] != str[j + 1]) j = pos[j];
12         if (str[i] == str[j + 1]) j++;
13         pos[i] = j;
14     }
15 }
16 int main()
17 {
18     int n,num=0;
19     while (cin >> n && n)
20     {
21         num++;
22         //if (num != 1) cout << endl;
23         string str;
24         cin >> str;
25         
26        
27         cout << "Test case #" << num << endl;
28         get_pos(str);
29         for (int i = 2; i <= str.size(); i++)
30         {
31             if (pos[i-1]!=-1&&(i % (i - pos[i - 1] - 1) == 0))//存在border是前提
32             {
33                 cout << i << " " << i / (i - pos[i - 1] - 1) << endl;
34             }
35         }
36         cout << endl;
37     }
38 }

---------------------------------------------------------------------------------------------------------------------------

HDU-3746-Cyclic Nacklace

http://acm.hdu.edu.cn/showproblem.php?pid=3746

题目大意:给出一字符串,要求问要补多少个字符才能凑出一个循环节构成的字符串

思路:算出next数组,然后找出周期,周期-(长度%周期)就是需要补充的数量

考虑到已经是循环节构成的字符串要输出0,没有border的要输出自身长度

(此题cin,memset好像不能用,题目有问题)

 1 #include <iostream>
 2 #include<string>
 3 #include<cstring>
 4 #include<stdio.h>
 5 using namespace std;
 6 int pos[100010];
 7 char str[100010];
 8 int main()
 9 {
10 
11     int n;
12     scanf("%d", &n);
13     getchar();
14     while (n--)
15     {
16         gets(str);
17         pos[0] = -1;
18         int len = strlen(str);
19         for (int i = 1; i < len; i++)
20         {
21             int j = pos[i - 1];
22             while (j >= 0 && str[i] != str[j + 1]) j = pos[j];
23             if (str[i] == str[j + 1])j++;
24             pos[i] = j;
25         }
26 
27         int cyl = len - (pos[len - 1] + 1);
28         int t = (cyl - (len % cyl)) % cyl;
29         if (t == 0 && len / cyl == 1) t += len;
30         
31         printf("%d
", t);
32     }
33 }

 

 

 

 

 

 

以上是关于KMP的初步认识及题目分析的主要内容,如果未能解决你的问题,请参考以下文章

初步学习JAVA面向对象初步认识及面向对象内存分析图举例说明

实验一:初步认识程序在内存中运行

JAVA-初步认识-第二章-字符类型的运算

初步认识Hadoop

JAVA-初步认识-第十二章-JVM中的多线程分析

DS04-图