KMP学习笔记
这是我学习kmp算法的个人理解
算法描述
- 变量说明:
- string pat :就是要匹配得到的子串——模式串
- string txt :就是被匹配的母串
- 算法目标:
这个算法的目标是在母串中找子串 - 常用思路:遍历匹配
即朴素的无回溯的模式匹配
但是遍历匹配得到的算法复杂度为n(O2),因为遍历会将小子串(肯定不是pat目标所得)反复进行比较,浪费了大量资源;于是我们对于给定的子串pat规定一种顺序,让匹配时不至于全部回退到首位,并建立一个next[]数组储存这个顺序,这样不至于完全遍历毫无意义的子串
算法思路
下面的话是我对这个算法的浅薄见解,如果不理解也不影响学习
因为text串前面已经匹配成功的子串和pat串前端是相同的,所以单独考虑par串就可以了:就是计划出来当 pat[i] 位不匹配时应该退回到哪个位置,而不用考虑test应该是什么
最终目标就是每一位 pat[i] 如果没匹配都可以找到回退几位
把匹配想像成点一串灯,只有匹配成功这个灯才亮,算法要保证灯亮的最多,即达到效率提高
从头开始,然后递归:如果不匹配,就转到上一次的处理方法。如果匹配,就到下一个状态
- 把上一次的处理方法用x来记录
void get_next(string pat, int next[]) {
//i表示当前正在匹配的下标
int i = 1;
next[1] = 0;
//shadow表示正在匹配的上一个位置,本质上也是下标,递归使用
int shadow = 0;
while (i < pat.length()) {
//当下标比pat小的时候,循环开始4
//pat[i] == pat[shadow]表示pat【i】和匹配的上一个位置相同——在匹配过程中呈现此位的字符和模式串字符相同,匹配成功
//这时i就跳转到下一格,同时shadow跟进跳转
if (shadow == 0 || pat[i] == pat[shadow]) {
++i;
++shadow;
next[i] = shadow;
}
//如果匹配不成功,shadow就在回退一个状态,回退到匹配为止
else
{
shadow = next[shadow];
}
}
}
要是还不懂的话,推荐看知乎大神的讲解,内含大量图片,gif解释用法
他用的是dp二维数组,但是原理都是一样的,下面是他的部分代码
int main() {
string pat;
//pat是要匹配的子串
string txt;
//txt是被匹配的串
cin >> pat;
cin >> txt;
int dp[100];
//dp是状态转移的描述,下标表示所取的pat的坐标
int x;
//x是影子状态,就是退回的最大位数
//此处为核心算法
{
//基础情况:输入的字符是首字符,状态1点亮
dp[0][pat[0]] = 1;
//此时影子状态为0
x = 0;
//影子状态就是递归,始终保持在当前状态的上一个状态
//一旦现在状态无法跳转到下一个,就转移到上一个状态应该转移的位置
//使用循环达成
for (int j = 0; j < pat.length(); j++) {
//在pat中每一个字符进行构建状态转移表格
for (int c = 0; c <= 256; c++) {
//对每一个ASCII码进行构建表格
//此处递归解释在上面
dp[j][c] = dp[x][c];
}
//当退出循环时,pat当前状态会前进一步
dp[j][pat[j]] = j + 1;
//所以影子状态也要前进一步
x = dp[j][pat[j]];
}
}
int M = pat.length();
int N = txt.length();
// pat 的初始态为 0
int j = 0;
for (int i = 0; i < N; i++) {
// 计算 pat 的下一个状态
j = dp[j][txt[i]];
// 到达终止态,返回结果
if (j == M) return i - M + 1;
}
// 没到达终止态,匹配失败
return -1;
}