文章目录
前言
一、KMP算法适用场景
二、例题,模板
1.模板
2.AcWing 831. KMP字符串
本题分析
AC代码
三、时间复杂度
前言
KMP算法新地址:数据结构:KMP(考研 + 竞赛),本文理解较浅,思路不清晰,可前往新地址去查看,为最新的理解。
一、KMP算法适用场景
KMP算法就是能快速找到一个模板串是否有子串的算法,这里我们直接用题目去分析,就本人目前而言,KMP算法感觉是最难理解的算法,如果不能理解,建议背下来模板先用,后续再慢慢理解
二、例题,模板
1.模板
本模板来自AcWing算法基础课
// s[]是长文本,p[]是模式串,n是s的长度,m是p的长度 //求模式串的Next数组: for (int i = 2, j = 0; i <= m; i ++ ) { while (j && p[i] != p[j + 1]) j = ne[j]; if (p[i] == p[j + 1]) j ++ ; ne[i] = j; } // 匹配 for (int i = 1, j = 0; i <= n; i ++ ) { while (j && s[i] != p[j + 1]) j = ne[j]; if (s[i] == p[j + 1]) j ++ ; if (j == m) { j = ne[j]; // 匹配成功后的逻辑 } }
2.AcWing 831. KMP字符串
本题链接:AcWing 831. KMP字符串
本博客给出本题截图:
本题分析
根据例子去讲解本算法,首先s数组存储的是模板串,p数组存储的是子串
接下来举一个栗子:
模板串(s):{aabaabaaf}
子 串 (p) :{aabaaf}
关于ne数组:ne[i]存储的是模式串中每个前缀最长的能匹配前缀子串的结尾字符的下标,同样也表示这一前缀和后缀最长相等的长度
KMP算法的实现:
s:aabaabaaf
p:aabaaf
012345678
我们通过观察可以知道在第5位的时候模板串和子串不匹配
按照最暴力的做法接下来我们应该把子串向后移动一个位置,即拿p[0]和s[1]进行比较,如果不能匹配,那么拿p[0]和s[2]比较,依次类推,这显然是很麻烦的
如果我们能预处理出来这么一个东西,即找到p[5]之前的元素的最长公共子序列:即对于{aabaa},找到最长公共子序列{aa},那么我们就可以进行如下操作:
直接把数组p平移为
aabaabaaf
aabaaf
就可以省去很多的步骤,从而达到优化代码的结果,接下来我们来看如何实现这种操作
计算数组p的每一位的ne
ne[0] = 0,显然对于单独一个字母,不存在子序列
ne[1] = 1,a和a匹配
ne[2] = 0,{a,a,b}中不存在前缀 = 后缀
ne[3] = 1,{a,a,b,a}中,a和a匹配
ne[4] = 2,{a,a,b,a,a}中,aa和aa披人皮
ne[5] = 0,{a,a,b,a,a,f}中不存在前缀 = 后缀
所以本例子中的ne数组就是{0,1,0,1,2,0}
然后我们把这个数组中的元素直接右移1变成{-1,0,1,0,1,2}(不需要理解为什么这么去做)
当我们p[5]和s[5]不匹配的时候,我们采取的操作是直接继续让s[5]去和p[2]进行比较即我们直接让s[5]去和p[ne[5]]去比较,假令遍历p数组的指针为j,那么这一步的操作就是让j = ne[j];,所以让我们子串与模板串不匹配的时候,执行这个操作就可以实现快速匹配
接下来说一下怎么求ne数组
for (int i = 2, j = 0; i <= n; i ++ ) { while (j && p[i] != p[j + 1]) j = ne[j]; if (p[j + 1] == p[i]) j ++; ne[i] = j; }
初始化ne[1] = 0;
AC代码
#include <iostream> using namespace std; const int N = 100010, M = 1000010; char s[M], p[N]; int ne[N]; int main() { int n, m; cin >> n >> p + 1 >> m >> s + 1; for (int i = 2, j = 0; i <= n; i ++ ) { while (j && p[i] != p[j + 1]) j = ne[j]; //注意j不能为0 if (p[j + 1] == p[i]) j ++; ne[i] = j; } for (int i = 1, j = 0; i <= m; i ++ ) { while (j && p[j + 1] != s[i]) j = ne[j]; //不匹配的话就执行操作,注意j不能为0,即返回到头的话就没必要继续执行j = ne[j]了,否则会死循环 if (p[j + 1] == s[i]) j ++; //匹配的话就让j指针往后移 if (j == n) //证明成功找到了子串 { cout << i - n << ' '; j = ne[j]; //继续遍历,因为满足条件的起始下标不止可能一个 } } return 0; }
三、时间复杂度
关于KMP算法的时间复杂度以及证明,后续会给出详细的说明以及证明过程,目前先鸽了。