KMP算法
KMP算法(Knuth-Morris-Pratt算法)是一种字符串匹配算法,用于在文本串中查找子串。KMP算法利用已知信息加速匹配过程,时间复杂度为O(m+n),其中m为模式串的长度,n为文本串的长度。
基本思想:
KMP算法的基本思想是,在匹配过程中当发现不匹配时,将模式串向右移动一定的距离,而不是直接从文本串的下一个位置开始匹配。(暴力的方法就是将模式串在文本串上向后移动一位)移动的距离取决于已匹配的部分字符串的最长公共前后缀,即前缀和后缀的最长匹配长度。
注:
(1)为什么移动的距离取决于已匹配的部分字符串的最长公共前后缀,即前缀和后缀的最长匹配长度:
因为已匹配的部分字符串的最长公共前后缀,即前缀和后缀的最长匹配长度,可以用来指示模式串在匹配过程中出现不匹配的位置。如果这个最长匹配长度为0,说明当前的不匹配位置不能向右移动,只能将模式串整体向右移动一位,从下一个位置开始匹配。但如果最长匹配长度不为0,我们就可以将模式串向右移动这个长度的距离,从最长匹配长度的后一个字符开始重新匹配,从而避免了不必要的匹配
具体来说,假设我们已经匹配了文本串的前i个字符和模式串的前j个字符,但第j+1个字符和第i+1个字符不匹配,此时我们需要将模式串向右移动一定的距离才能继续匹配。这个移动的距离就是已匹配部分字符串的最长公共前后缀长度,因为这个长度告诉我们模式串的前缀和后缀是重复的,也就是说模式串前面的一部分可以直接跳过,直接匹配已匹配部分字符串的后缀的下一个字符。
举例:
补:或者上图也可以这么理解:不匹配是出现在后缀a a的后面,又因为前缀a a与后缀相同,所以可以把前缀移过来与原后缀对应,移动后再将现在位置的文本串的前缀的后一位与刚刚模式串的位置比较
说白了就是把原来的前缀移到现在的后缀位置
核心:
KMP算法的核心是求出模式串中每个前缀的最长公共前后缀长度,这可以通过构建前缀表(也称为失配函数)来实现。前缀表中的每个值表示模式串中该位置前缀的最长公共前后缀长度。
具体代码实现:(这种方法坚持的循环不变量是看前一位)
(1)初始化:
i:后缀末尾位置,先赋为1
j:不仅是前缀末尾位置,还是i(包括i)之前子串的最长相等前后缀长度,先赋为0
next数组:记录当前位置(包括当前位置)子串最长相等前后缀长度,next[0] = 0
int j = 0;
next[0] = j;
for (int i = 1; i < s.size(); i++) {
}
(2)前后缀末尾不同时
j向前回退,直到j指向位置的字母与i相同或j退回next数组开头
while (j > 0 && s[i] != s[j]) { // 前后缀不相同了
j = next[j - 1]; // 向前回退
}
(3)前后缀末尾相同时
j的值先递增再赋给next[i]
if (s[i] == s[j]) { // 找到相同的前后缀
j++;
}
next[i] = j;
完整代码
void getNext(int* next, const string& s) {
int j = 0;
next[0] = 0;
for(int i = 1; i < s.size(); i++) {
while (j > 0 && s[i] != s[j]) { // j要保证大于0,因为下面有取j-1作为数组下标的操作
j = next[j - 1]; // 注意这里,是要找前一位的对应的回退位置了
}
if (s[i] == s[j]) {
j++;
}
next[i] = j;
}
}
主要思路:
(1)构造next数组
(2)匹配模式串与文本串
代码实现:
class Solution {
public:
void getNext(int* next, const string& s) {
int j = 0;
next[0] = 0;
for(int i = 1; i < s.size(); i++) {
while (j > 0 && s[i] != s[j]) {
j = next[j - 1];
}
if (s[i] == s[j]) {
j++;
}
next[i] = j;
}
}
int strStr(string haystack, string needle) {
if (needle.size() == 0) {
return 0;
}
int next[needle.size()];
getNext(next, needle);
int j = 0;
for (int i = 0; i < haystack.size(); i++) {
while(j > 0 && haystack[i] != needle[j]) {
j = next[j - 1];
}
if (haystack[i] == needle[j]) {
j++;
}
if (j == needle.size() ) {
return (i - needle.size() + 1);
}
}
return -1;
}
};