简单理解kmp
由于脑子不够用,所以只能粗略的说一下kmp
kmp是用于做字符串匹配的
应用: 1.匹配模式串在主串中第一个位置 2.模式串在主串中出现的次数 3.求循环节长度/ 最小覆蓋子串长度 4.求串的最大前缀-后缀或次数或每个长度(即相同的前缀和后缀) 等等
首先我们先想一下用暴力进行匹配(tlen是模式串的长度,slen是主串的长度)
从左到右依次匹配,首先是从0到0+tlen,然后1到1+tlen……i到i+tlen 这样的话复杂度就很高为 O(slen*tlen)
运用kmp算法主要是解决对于指向模式串的下标的回溯问题.不理解看下面
要理解KMP的关键是要知道next数组的含义. next[i]是存放前 i个字符中前缀和后缀匹配的最大长度,如下
在模式串 abcab中 对应的next数组为
下标 | 0 | 1 | 2 | 3 | 4 | 5 |
字符 | a | b | c | a | b | |
next | -1 | 0 | 0 | 0 | 1 | 2 |
一定要注意next[i]数组的含义
在主串 abcacbbabcabca中要匹配模式串
a b c a c b b a b c a b c a |
a b c a b |
从左向右依次匹配 匹配了前四个字符之后第五个字符不匹配,那么就需要进行下一步. .如果下一步从主串第二个元素b开始的话是很不合适的,因为很明显模式串的第一个字符是a.
那么从哪开始呢. 也很明显是从主串的第四个元素a开始更为合适..
我们之前在kmp算法之前处理了next数组 是的next[4]是1 ,这是什么意思. 在模式串中前4个字符长度为1的前缀和长度为1的后缀是匹配的.. next[4]是我们事先处理好的. 所以这里直接将指向模式串的下标指向next[4]. 即如下图
a b c a c b b a b c a b c a |
a b c a b |
此刻我们不需要再考虑红色之前的是否匹配. 因为这是上面可以保证匹配的.
(这里就是kmp算法的关键.在此不太懂一定要再去理解一下next数组的含义)
kmp算法模板的代码如下 HDU – 1711 HDU – 1686 HDU – 2087 这是一些模板题
void getNext(){//a数组是主串 b数组是模式串
Next[0] = -1;
int j = -1;
int i = 0;
while (i < tlen && j < tlen){
if (j == -1 || b[i] == b[j]){
Next[++i] = ++j;
} else{
j = Next[j];
}
}
}
int KMP(){
int j = 0;
int i = 0;
getNext();
while (i < slen && j < tlen){
if (j == -1 || a[i] == b[j]){//如果a[i]和b[j]匹配了,则继续向后判断
j++; i++;
} else{//如果不匹配,更新指向模式串的下标
j = Next[j];//将next[j]赋值给模式串的下标 以便匹配 除已经匹配的前缀外 的所有字符
}
}
if (j == tlen){
return i - j + 1;
} else{
return -1;
}
}
模式串在主串中出现的次数
在匹配完成结束后不能立即退出循环.应该利用next数组 将下标改为下一个地方
int KMP(){
int res = 0;
int num = 0;
int i = 0, j = 0;
getNext();
while (i < slen){
if (j == tlen){
num++;
j = Next[j];
}
if (j == -1 || S[i] == P[j]){
++i; ++j;
} else{
j = Next[j];
}
}
if (j == tlen) num++;
return num;
}
循环节
含义:如果无限小数的小数点后,从某一位起向右进行到某一位止的一节数字循环出现,首尾衔接,称这种小数为循环小数,这一节数字称为循环节
通俗的讲也就是一个周期
假设字符串S的长度为len,则S存在最小循环节, 循环节的长度L=len-next[len],
如果len可以被len-next[len]整除,则表明字符串S可以完全由循环节循环节组成
如果不能,说明还需要再添加几个字母才能补全. 需要补的个数是循环个数 L-len%L = L-(len-L)%L = L-next[len]%L.
参考博客 https://www.cnblogs.com/chenxiwenruo/p/3546457.html
next数组的应用
找出所有的前缀-后缀 POJ – 2752
其实挺简单,主要是看你对next数组的理解程度如何
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
字符 | a | b | a | b | c | a | b | a | b | a | b | a | b | c | a | b | a | b | |
next | -1 | 0 | 0 | 1 | 2 | 0 | 1 | 2 | 3 | 4 | 3 | 4 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
仔细观察会发现next[18]=9 是指前缀ababcabab 和后缀匹配
next[9]=4是前缀abab和后缀匹配 这里的下标从0-8的字符串S1为ababcabab 正好就是上一个的后缀S2. 所以S1的前缀和后缀匹配就是S1的前缀和S2的后缀匹配. 也就是 整个串的前缀和后缀匹配. 发现了这个规律. 看是否能够实现呢?
#include<iostream>
#include<cstdio>
#include<string.h>
using namespace std;
typedef long long ll;
const int MAXN = 400000;
const int INF = 0x3f3f3f3f;
char str[MAXN + 100];
int Next[MAXN + 100];
int a[MAXN];
void getNext(int len){
int i = 0;
int j = -1;
Next[0] = -1;
while (i < len && j < len){
if (j == -1 || str[i] == str[j]){
i++; j++;
Next[i] = j;
} else{
j = Next[j];
}
}
}
int main(){
while (scanf("%s", str) != EOF){
int len = strlen(str);
getNext(len);
int m = len;
a[0] = len;
int index = 1;
while (Next[m]){
a[index++] = Next[m];
m = Next[m];
}
for (int i = index - 1; i >= 0; i--){
if (i != index - 1) printf(" ");
printf("%d", a[i]);
}
printf("\n");
}
return 0;
}
找出所有前缀在字符串中出现的次数51Nod – 1277
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
字符 | a | b | a | b | a | b | a | |
next | -1 | 0 | 0 | 1 | 2 | 3 | 4 | 5 |
长度为7的出现一次 next[7]=5 长度为5的前缀在长度为7的字符串中出现一次 此时res[7]=1 res[5]=res[7]=1
长度为6的出现一次 next[6]=4 长度为4的前缀在长度为6的字符串中出现一次 此时res[6]=1 res[4]=res[6]=1
长度为5的出现一次 next[5]=3 长度为3的前缀在长度为5的字符串中出现一次 此时res[5]=2 res[3]=res[5]=2
长度为4的出现一次 next[4]=2 长度为2的前缀在长度为4的字符串中出现一次 此时res[4]=2 res[2]=res[4]=2
长度为3的出现一次 next[3]=1 长度为1的前缀在长度为3的字符串中出现一次 此时res[3]=3 res[1]=res[3]=3
长度为2的出现一次 next[2]=0 长度为0不算前缀 res[2]=3
长度为1的出现一次 同2 res[1]=4
综上所述 长度为1出现4次, 2出现3次 3出现3次 4出现2次 5出现2次 6出现1次 7出现1次
所以可以得到如下的代码
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<string.h>
using namespace std;
typedef long long ll;
const int MAXN = 100000;
const int INF = 0x3f3f3f3f;
char str[MAXN + 1000];
int Next[MAXN + 1000];
int len;
ll res[MAXN + 1000];
void getNext(){
int i = 0; int j = -1;
Next[0] = -1;
while (i < len && j < len){
if (j == -1 || str[i] == str[j]){
++i; ++j;
Next[i] = j;
} else{
j = Next[j];
}
}
}
int main(){
while (scanf("%s", str) != EOF){
len = strlen(str);
getNext();
for (int i = len; i >= 1; i--){
res[i]++;
res[Next[i]] += res[i];
}
ll maxx = -INF;
for (ll i = 1; i <= len; i++){
maxx = max(maxx, res[i] * i);
}
printf("%lld\n", maxx);
}
return 0;
}