KMP总结
总结
- 详细原理网上一大堆,这里只简单的总结一下自己的理解
- KMP匹配前需要的有主串a,模式串b,数组next
- 普通匹配会浪费时间,比如匹配好了一大串字符了,最后一个不匹配,下一次匹配要从这次匹配主串的起点的下一个字符开始匹配,而实际上刚刚匹配好的一大串字符,可能不需要再匹配
- 这里先为主串分配指针i,为模式串分配指针j
- 普通匹配就是i会回溯,j也会回溯(第3点)
- KMP匹配利用数组next让i不需要回溯,j回溯
- 数组next和模式串一一对应,记录当模式串某个字符不匹配时,j回溯的位置
- 比如b="abcabd",匹配时a[i]='c'和b[j]='d'不匹配,next[j]=2,这时j从5回溯到2,因为前缀都有"ab"不需要在匹配一次
- 构造数组next的函数getNext,原理我自己也很难说通,都是结合代码来熟悉
-
构造过程是模式串b进行自我匹配,为模式串b分配指针i和指针j
-
next[j]要回溯到哪里,取决于前缀相同位数
-
i<b.length()-1作为循环结束条件:两个字符匹配了是确定下一个字符的回溯位置
-
如果匹配上了,指针i和指针j前进后,确定next[i]=j
-
如果当前两个字符匹配了,b[i]==b[j],那么我们就能确定当下一个字符b[j+1]不匹配时j要回溯到哪里(next[i]==j),因为前缀相同才能匹配到当前位置,j+1不匹配,那就回溯到上一个前缀相同的位置
-
如果匹配不上,指针j回溯
- 回溯过程,可能回溯到next[0],这是 j==-1 说明指针j回溯到0时也没和b[i]匹配上,所以指针i和指针j前进
- j==-1 进入匹配上了的循环是因为,匹配到i-1时已经确定了i的回溯位置,所以在匹配i的时候,是为i+1找回溯位置
- 都可以++i,++j是因为j+1后,j=0,假设i=3,在KMP匹配中,b[3]不匹配时,指针j回溯到b[0]再进行匹配
-
- kmp匹配和求next数组的过程差不多
- 主串a的指针i和模式串的指针j都是从0开始
- 如果匹配上了,指针i和指针j前进
- 如果匹配不上,指针j回溯
- 指针j回溯到-1,说明指针j回溯到0时也没和主串匹配上,主串上的a[i]和模式串任何一个字符都不匹配,所以指针i和指针j前进
代码
#include <iostream>
#include <vector>
#include <string>
using namespace std;
void getNext(string b, vector<int>& next) {
int i = 0;
int j = -1;
next[i] = j;
while (i < b.length() - 1) {
if (j == -1 || b[i] == b[j]) {
++i;
++j;
next[i] = j;
}
else j = next[j];
}
}
int KMP(string a, string b, vector<int> next) {
int i = 0, j = 0;
while (i < a.length() && j < (int)b.length()) {
if (j == -1 || a[i] == b[j]) {
++i;
++j;
}
else j = next[j];
}
if (j == b.length()) return i - j;
return -1;
}
int main() {
string a = "abcxabcdabxabcdabcdabcy";
string b = "abcy";
vector<int> next(b.length());
getNext(b, next);
cout << "next数组:" << endl;
for (int i = 0; i < next.size(); i++) cout << next[i] << " ";
int ans=KMP(a, b, next);
cout << endl << "匹配到主串位置为:" << ans << endl;
return 0;
}