KMP算法
基本原理
目标:找出字符串pattern在字符串text中出现的次数与位置,
方法:先计算pattern字符串的前缀表,即next[]数组,然后再使用next数组来进行字符串匹配
Tips:
1.next[i] 的含义:前面长度为i的子串的最大公共前后缀的长度
代码实现
//经典KMP算法
//找出字符串p在字符串t中出现的次数与位置
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 1e6+10;
char p[N],t[N]; //p为短的字符串,t为长的字符串
int next[N]; //数字next为前缀码表
int sum; //字符串p在字符串t中出现的次数
void getNext(const char P[], int next[]) {
int m = strlen(P);
int i = 0, j;
j = next[0] = -1;
while (i < m) {
while (-1 != j && P[i] != P[j])j = next[j];
j++;i++;
next[i] = j;
}
}
void kmp(const char P[], const char T[] , int next[]) {
int n = strlen(T), m = strlen(P);
int i, j;
getNext(P, next);
i = j = 0
;
while (i < n) {
while (-1 != j && T[i] != P[j])j = next[j];
i++; j++;
if (j >= m) {
sum++;
printf("%d\n", i);
j = next[j];//这儿修改很重要,不然会超时
}
}
}
int main()
{
strcpy(p,"aba");
strcpy(t, "ababcabcbababcabacaba");
kmp(p,t,next);
printf("字符串匹配次数为:%d\n", sum);
//system("pause");
return 0;
}
KMP算法的应用
next数组的应用
来源:POJ2752 Seek the Name, Seek the Fame
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 4 * 1e5 + 10;
void getNext(char P[], int next[])
{
int m = strlen(P);
int i, j;
i = 0;
j = next[0] = -1;
while (i < m)
{
while (j != -1 && P[i] != P[j]) j = next[j];
j++; i++;
next[i] = j;
}
}
int main()
{
char S[N];
int next[N];
vector<int> a; //保存满足条件的姓名的长度
while (scanf("%s", S)!= EOF)
{
a.clear();
int len = strlen(S);
a.push_back(len);
memset(next,0, sizeof(next));
getNext(S, next);
while (next[len] > 0)
{
a.push_back(next[len]);
len = next[len];
}
sort(a.begin(), a.end());
for (int i = 0; i < a.size(); i++)
cout << a[i] << " ";
cout << endl;
}
return 0;
}
最小循环节和循环周期
例一
来源:POJ2406 Power Strings
思路:根据定理可以得到:假设字符串
S
S
S的长度为
l
e
n
len
len,那么 最小循环节
L
=
l
e
n
−
n
e
x
t
[
l
e
n
]
L=len-next[len]
L=len−next[len]. 如果
L
L
L可以整除
l
e
n
len
len,那么循环周期
T
=
l
e
n
/
L
T=len/L
T=len/L. 否则该字符串不能由循环节循环得到,输出1.
#include <iostream>
#include <string.h>
using namespace std;
const int N = 1e6 + 10;
void getNext(char P[], int next[])
{
int m = strlen(P);
memset(next, 0, sizeof(next));
int i, j;
i = 0;
j = next[0] = -1;
while (i < m)
{
while (j != -1 && P[i] != P[j]) j = next[j];
i++; j++;
next[i] = j;
}
}
int main()
{
char S[N];
int next[N];
int L, T;//L为最小循环节的长度,T为循环周期
while (scanf("%s", S) != EOF)
{
if (!strcmp(S, ".")) break; //strcmp(str1,str2) :若 str1<str2 则返回负数;若str1>str2 则返回正数;若str1=str2 则返回0
getNext(S, next);
int len = strlen(S);
int L = len - next[len];
if (len%L == 0)
cout << len / L << endl;
else
cout << 1 << endl;
}
return 0;
}
例二
来源:POJ1961 Peroids
思路:此题为例一的扩展,求出由给定字符串的前 i 个字符组成的子串的循环节和循环周期。遍历整个字符串就能求出,遍历时候的循环节长度
L
=
i
−
n
e
x
t
(
i
)
L=i-next(i)
L=i−next(i)
//最小循环节与循环周期问题
//最小循环节: L = len - next(len)
//循环周期: T = len/L (L可以整除len)
#include <iostream>
#include <string.h>
using namespace std;
const int N = 1e3 + 10;
void getNext(char P[], int next[])
{
int m = strlen(P);
memset(next, 0, sizeof(next));
int i, j;
i = 0;
j = next[0] = -1;
while (i < m)
{
while (j != -1 && P[i] != P[j])
j = next[j];
j++; i++;
next[i] = j;
}
}
int main()
{
int next[N];
char S[N];
char s1[N];
int L, T; //L为最小循环节,T为循环周期
int k;
int q = 0;
while (scanf("%d", &k) != EOF)
{
if (k == 0) break;
q++;
scanf("%s", S);
cout << "Test case #" << q << endl;
getNext(S, next);
for (int i = 2; i <= k; i++)
{
L = i - next[i];
if (i%L == 0&& L<i)
{
T = i / L;
cout << i << " " << T << endl;
}
}
}
system("pause");
return 0;
}