KMP算法用于快速查找模式串在文本串中出现的位置,其最大的优势就是可以利用已经匹配过的一部分内容来得知在出现模式串和文本串不匹配时,模式串应该回退到哪个位置。
而实现这一功能依赖于next数组!
next数组是模式串的前缀表,它的长度等于模式串的长度。next[ i ]表示模式串中从0到i这个子串的最大相等前后缀的长度。
前缀 : 不包括第一个字符的其余所有子串
后缀 : 不包括最后一个字符的其余所有子串
例如aaba的前缀有:a、aa、aab;后缀有:a、ba、aba。
所以a的最大相等前后缀的长度是0,aa的最大相等前后缀的长度为1, 以此类推,aab对应0,aaba对应1,所以aaba的前缀表就为0 1 0 1
下面来模拟一下KMP算法的过程
模式串:aabaaf,next数组为0 1 0 1 2 0
文本串:aabaabaaf
当i=5,j=5时,不匹配,那么j之前的都是匹配过的,我们现在通过next数组可以知道j之前的这个子串的最大相等前后缀的长度是2,那么就说明子串的前两个字符和子串的后两个字符是完全一样的,匹配失败的位置是后缀的后面,那么j就要回退到前缀后面的一个字符的位置上继续匹配,而这个位置的下标刚好就是最大相等前后缀的长度,所以j=next[ j-1]
#include <iostream>
#include <vector>
#include <string>
using namespace std;
void getNext(vector<int> &next, string pattern)
// 求得模式串pattern的前缀表(next数组)
{
// 1.先进行初始化
int j = 0, i; // j指向前缀的末尾,i指向后缀的末尾
next[0] = 0; // 初始化next数组
for (i = 1; i < pattern.size(); i++)
{
// 2.处理前缀不相等情况;
while (j > 0 && (pattern[j] != pattern[i])) // 前后缀不相等,则j持续回退,直到pattern[i]==pattern[j]或者j=0;
{
j = next[j - 1];
}
// 3.前后缀相等;
if (pattern[i] == pattern[j])
{
j++;
}
// 4.更新next数组;
next[i] = j;
}
}
void KMP(string text, string pattern)
{
// 串为空直接返回
if (pattern.empty() || text.empty())
return;
// 定义next数组
vector<int> next(pattern.size());
// 构建next数组
getNext(next, pattern);
// 开始匹配
int i = 0, j = 0; // i指向文本串,j指向模式串
for (i; i < text.size(); i++)
{
// 1.不匹配
while (j > 0 && (text[i] != pattern[j]))
{
j = next[j - 1];
}
// 2. 匹配
if (text[i] == pattern[j])
{
j++;
}
// 3.匹配成功
if (j == pattern.size())
cout << i - pattern.size() + 1 << " ";
}
}
int main()
{
string text, pattern;
cout << "请输入文本串:";
cin >> text;
cout << "请输入模式串:";
cin >> pattern;
cout << "模式串在文本串中出现过的位置:";
KMP(text, pattern);
return 0;
}