本篇是小编自学KMP算法时的见解和笔记,有不好的地方欢迎指出!
一、解决的问题:
就是解决文本串的匹配问题。
二、几个概念:
(1)文本串:即原字符串,也就是总字符串。也叫主串
(2)模式串:即目标串,也即是我们要找的字符串。也叫子串
(3)前缀表:用以找到正确匹配过的内容
(4)前缀与后缀,最长相等前后缀
例:(本篇文章全都是以这个例子展开)
文本串:a a b a a b a a f
模式串: a a b a a f
前缀:包含首字母,不包含尾字母的所有子串。比如在这里,a, aa, aab, aaba, aabaa 都是前缀
后缀:只包含尾字母,不包含首字母的子串。,比如在这里,f, af, aaf, baaf, abaaf 都是后缀
最长相等前后缀:从前到后依次找最长相等的前缀与后缀(实际上就是指相等的那段字符长度)
这里:从前向后,依次是: a, aa, aab, aaba, aabaa, aabaaf
他们的前后缀依次是: 0,1 ,0 , 1 , 2 , 0
这里解释一下原因:
对于a,它没有前缀和后缀,所以最长相等前后缀为0
对于aa,相当于有前缀a,与后缀a,长度为1,所以最长相等前后缀为1
对于aab,前缀a,aa,与后缀b,ab都不相等,所以最长相等前后缀为0
对于其他的一样记录
(5)next数组:记录模式串的最长相等前后缀。这里就是next [ 6 ] = { '0', '1' , '0' , '1' , '2' , '0' }
next数组中的值就是下次要比较的模式串的元素的下标
三、next数组
(I)next数组的不同实现方法
(1)第一种方法:next [ 6 ] = { '0', '1' , '0' , '1' , '2' , '0' }
(2)第二种算法:next [ 6 ] = { '-1', '0', '1' , '0' , '1' , '2' } (原数字右移一位)
(3)第三种方法:next [ 6 ] = { '-1', '0' , '-1' , '0' , '1' , '-1' } (原数组全减1)
(II)确定next数组(方法一的代码)
/*
i代表后缀末尾位置,
j代表前缀末尾位置,还代表着i之前,包括i在内的子串的最长相等前后缀的长度
s是模式串
*/
void build_next(int *next, char *s, int n)
{
int j = 0, i;
next[0] = 0;
for (i = 1; i < n; i++) // 注意i从1开始
{
while (j > 0 && s[i] != s[j]) // j要保证大于0,因为下面有j-1作为数组下标的操作前后缀不相同了
{
j = next[j - 1]; // 注意这里,是要找前一位的对应的回退位置
}
if (s[i] == s[j]) // 找到相同的前后缀
{
j++;
}
next[i] = j; // 将j(前缀的长度)赋给next[i]
}
}
四、代码实现
(这里写的代码有点麻烦,只是适用于已经知道串的长度的情况,后续会进一步完善)
#include <bits/stdc++.h>
using namespace std;
/*
i代表后缀末尾位置,
j代表前缀末尾位置,还代表着i之前,包括i在内的子串的最长相等前后缀的长度
s是模式串
*/
void build_next(int *next, char *s, int n)
{
int j = 0, i;
next[0] = 0;
for (i = 1; i < n; i++) // 注意i从1开始
{
while (j > 0 && s[i] != s[j]) // j要保证大于0,因为下面有j-1作为数组下标的操作前后缀不相同了
{
j = next[j - 1]; // 注意这里,是要找前一位的对应的回退位置
}
if (s[i] == s[j]) // 找到相同的前后缀
{
j++;
}
next[i] = j; // 将j(前缀的长度)赋给next[i]
}
}
void FindStr(char *Str, char *str, int *next, int t, int n)
{
int i = 0; // 文本串中的指针(下标)
int j = 0; // 模式串中的指针(下标)
while (i < t)
{
if (Str[i] == str[j]) // 如果字符相匹配,那么比较下一个
{
j++;
i++;
}
else // 如果字符不匹配
{
if (j > 0) // 如果不是模式串的第一个字符,那么就前移
j = next[j - 1];
else
i++;
}
if (j == n) // 如果相等,说明模式串匹配成功,输出文本串中与模式串相同的第一个字符的下标
{
cout << i - j << endl;
break;
}
}
if (j != n) // 如果不相等,说明没匹配成功,输出NOT FOUND
cout << "NOT FOUND" << endl;
}
int main()
{
int t, n, i, j;
cin >> t;
char Str[t]; // 定义文本串
for (i = 0; i < t; i++)
cin >> Str[i];
cin >> n;
int next[n];
char str[n]; // 定义模式串
for (i = 0; i < n; i++)
cin >> str[i];
build_next(next, str, n); // 创建next数组,方便使用
FindStr(Str, str, next, t, n);
return 0;
}