前言
看完很多讲解KMP算法的博客和视频,还是依旧懵懵懂懂,随后我根据代码反推了一遍过程才大致了解了KMP的实现过程。
提示:以下是本篇文章正文内容,仅是个人的理解,方便今后的复习。第一次尝试写博客,能力有限,敬请雅正。
一、得到next数组
我们都知道KMP算法中,难以理解的就是找到next数组,我分为以下两点进行理解。
1.为什么要找最长公共前后缀?
前后缀的概念
一个字符串的前缀是从第一个字符开始的连续若干个字符,例如ABCAB,有4个前缀,分别是
A | AB | ABC | ABCA |
---|
有4个后缀,分别是
B | AB | CAB | BCAB |
---|
则该字符串的最长公共前后缀为AB
举一个简单的例子,假设主串为:
模式串为:
将主串与模式串进行匹配,发现到此处不相匹配,而此时模式串的最长公共前后缀即为两个方框部分。
如果我们移动模式串,我们会发现,在前缀部分移动到后缀部分之前,模式串是不可能与主串相匹配的。
所以我们进行下一步匹配时,直接将前缀部分移动到后缀部分即可。
2.如何找最长公共前后缀,得到next数组
因为next数组是针对模式串的移动的,所以要从模式串中找最长公共前后缀
先上代码(示例):
//p为模式串,ne为next数组
void get_Next()
{
int i = 0, j = -1;
ne[0] = -1;
//n为模式串的长度
while (i < n)
{
if (j == -1 || p[i] == p[j])
{
i ++, j ++;
ne[i] = j;
}
else
j = ne[j];
}
}
因为要找不匹配点前的最长公共前后缀,定义i,j两个指针,让next[0] = -1,初始状态如下
通过上述代码一步步推演,得出next的值
当j != -1 ,并且p[i]!=p[j]时,j = next[0] = -1
接一下来继续按照程序一步步推演即可
此时p[i] == p[j] 即 i++, j++
到最后得到next数组
二、得到next数组之后进行匹配
依旧先上代码(示例):
void KMP_search()
{
//主串和模式串皆从0开始
//定义i为主串指针,j为模式串指针
int i = 0, j = 0;
//m为主串的长度
while (i < m)
{
if (j == -1 || s[i] == p[j])
i ++, j ++ ;
else
j = ne[j];
if(j == n)
printf("%d ", i - j);
}
}
提示:i为主串小标,j为模式串的下标,next数组表示模式串的移动
开始匹配到此处停止
移动模式串,即将模式串前缀部分移动到后缀部分,此时i = 5, j = 5, next[5] = 2
此时依旧不匹配,继续移动模式串,next[2] = 0
此时next[0] = -1,i,j同时++
此处开始匹配一直到最后匹配成功!
接下来附上一道acwing上的模板题链接:KMP字符串
三、字符串循环节
对于某一字符串S[1~i],在它众多的next[i]的“候选项”中,
如果存在某一个next[i],
使得: i % (i−next[i])==0 ,
那么 S[1~ (i−next[i])] 可以为 S[1 ~ i] 的循环节,
而 i/(i−next[i])即是它的循环次数 K
假设字符串长度为len,那么最小循环节的长度为len - ne[len]
证明过程感兴趣的话可以自行百度。
接下来附上一道关于KMP求循环节的题目:周期
题解代码如下:
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <deque>
#include <unordered_map>
using namespace std;
typedef long long ll;
typedef pair<int, int> P;
const int N = 1e5 + 6, M = 1e6 + 6;
const int inf = 0x3f3f3f3f;
int n, ne[M], cnt = 1;
char s[M];
void get_next()
{
int i = 0, j = -1;
ne[0] = -1;
while (i < n)
{
if (j == -1 || s[i] == s[j])
{
i ++, j ++;
ne[i] = j;
}
else
j = ne[j];
}
}
int main()
{
while (scanf("%d", &n), n != 0)
{
scanf("%s", s);
get_next();
printf("Test case #%d\n", cnt ++);
for (int i = 2; i <= n ; i ++ )
{
//根据题目输出样例要求这个next数组的值不能为0
if (ne[i] && i % (i - ne[i]) == 0)
printf("%d %d\n", i, i / (i - ne[i]));
}
printf("\n");
}
return 0;
}
总结
KMP算法,只看描述的话比较抽象也难以理解,个人觉得结合着算法代码和样例进行反推会更容易理解。