KMP笔记
xdg想骗吃骗喝,奈何本人能力不足,没能实现他的愿望。(_)
所以开始恶补kmp,并写下心得?
-
实现功能
实现两个字符串的区配,并实现相关功能。(目前遇到的情况有点少,以后多了一定补上。)
-
暴力区配法
想到字符串区配,我相信绝大部分人想到的是暴力搜索法。即一个个字符串相区配。既然容易想,但也有弊端,比方说 超时 ,非常容易想到最坏的情况是一个字符串区配到最后才适配上。其复杂度是O(mn)。
m是字符串a的长度,n是字符串b的长度。
所以,我建议,在读题时,要注意题目中给予的范围。几千什么的还好说。一旦上百万甚至更多,用这个办法肯定会超时的。用时请三思。
你认为我会贴代码?
我相信你动动脑子肯定打出来了.
接下来才是重头戏.
3.kmp区配法
讲之前建议听一下B站一个大佬的讲解,可能要听3遍哦,因为我听了3遍才明白了其运行原理/(ㄒoㄒ)/~~
[汪都能听懂的KMP字符串匹配算法【双语字幕】](https://www.bilibili.com/video/av3246487?from=search&seid=17082105778854054822)
看完,你应该明白了kmp相对与暴力缺少了逐个区配,而是通过next[]数组来实现跳跃区配,正如视频中讲到的,在进行next[]标记的过程中时间复杂度为O(m),而在区配中一个个区配,并没有从头再来,只需要一次即可.所以他的时间复杂度是O(m+n). //(如果你不记得这话,建议亲再去看一遍(_))
码字不易, 以下内容是一位大佬写的核心.
[很详尽KMP算法(厉害)](https://www.cnblogs.com/ZuoAndFutureGirl/p/9028287.html)
- 假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置
- 如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++,继续匹配下一个字符;
- 如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]。此举意味着失配时,模式串P相对于文本串S向右移动了j - next [j] 位。
- 换言之,当匹配失败时,模式串向右移动的位数为:失配字符所在位置 - 失配字符对应的next 值,即移动的实际位数为:j - next[j],且此值大于等于1。
很快,你也会意识到next 数组各值的含义:代表当前字符之前的字符串中,有多大长度的相同前缀后缀。例如如果next [j] = k,代表j 之前的字符串中有最大长度为k
此也意味着在某个字符失配时,该字符对应的next 值会告诉你下一步匹配中,模式串应该跳到哪个位置(跳到next [j] 的位置)。如果next [j] 等于0或-1,则跳到模式串的开头字符,若next [j] = k 且 k > 0,代表下次匹配跳到j 之前的某个字符,而不是跳到开头,且具体跳过了k 个字符。
如果你对这些过程感到很熟悉的话,上面视频的大佬讲的基本也是这个过程.如果想加深记忆的话建议结合这一段再看一遍
那么,问题的关键来了,什么求next[]数组呢?
下面先来个表
[外链图片转存失败(img-ffaZU2rK-1568277584034)(C:\Users\91515\Desktop\20140725231726921.jpg)]
看出来了吧
那什么实现呢
不多说上代码(其过程是和上面视频的过程讲的差不多,不清楚过程的话可以再看一遍)
void findnext(){
int len = strlen (s);
int a=-1,b=0;
next[0]=-1;//为啥不是0,0前进是1,那0的地位何在?
for(b=0;b<len;){
if(s[a]==s[b]||a==-1){//对比两个字符是否相等,若是则前进一位,并在相应的位置作下标注,要跳跃到的位置.
a++;
b++;
next[b]=a;
}else{//不适配时则后退都上一个字符中next[]所标记的位置
a=next[a];
}
}
}
上样题
period
For each prefix of a given string S with N characters (each character has an ASCII code between 97 and 126, inclusive), we want to know whether the prefix is a periodic string. That is, for each i (2 <= i <= N) we want to know the largest K > 1 (if there is one) such that the prefix of S with length i can be written as A K ,that is A concatenated K times, for some string A. Of course, we also want to know the period K.
Input
The input consists of several test cases. Each test case consists of two lines. The first one contains N (2 <= N <= 1 000 000) – the size of the string S.The second line contains the string S. The input file ends with a line, having the
number zero on it.
Output
For each test case, output "Test case #" and the consecutive test case number on a single line; then, for each prefix with length i that has a period K > 1, output the prefix size i and the period K separated by a single space; the prefix sizes must be in increasing order. Print a blank line after each test case.
Sample Input
3
aaa
12
aabaabaabaab
0
Sample Output
Test case #1
2 2
3 3
Test case #2
2 2
6 2
9 3
12 4
这是一道经典题,题意是:给定一个长度为n的字符串s,求它的每个前缀的最短循环节
想一下,那不就是有一个相同的next[]下标,有几个吗,只需要把字符串遍历一遍就可以了
上代码
#include<cstdio>
#include<cstring>
using namespace std;
int next[1000000];
char s[1000000];
void findnext(){
int len = strlen (s);
int a=-1,b=0;
next[0]=-1;
for(b=0;b<len;){
if(s[a]==s[b]||a==-1){
a++;
b++;
next[b]=a;
}else{
a=next[a];
}
}
}
int main(){
int a;
int Case=1;
while(~scanf("%d",&a)){
if(a==0){
break;
}
scanf("%s",&s);
findnext();
printf("Test case #%d\n",Case);
Case++;
for(int i=1;i<=a;i++){
int ans=i-next[i];
if((i%ans==0)&&next[i]>0){
printf("%d %d\n",i,i/ans);
}
}
printf("\n");
}
return 0;
}