KMP算法的精髓就是next数组,必须充分理解这个next数组。
next[j]的含义就是j的真前缀中能够自匹配的最大前缀和后缀,相当于在失配的情况下
能够排除很多不必要的匹配过程。
构造next数组用递推:
void get_next (int *p) { int t; t = next[0] = -1; int j = 0; while (j < m) { if (t < 0 || p[j] == p[t]) {//匹配 j++, t++; next[j] = t; } else //失配 t = next[t]; } }
比如0 0 0 0 0 0 0 1个文本串和0 0 0 1这个模式串,在对齐位置
0 0 0 0 0 0 0 1
0 0 0 1 下,每一次借助next数组模式串只会移动一格,本质上是因为没有应用
失配情况下相同的字符必然也失配这种规律,所以我们修改下next数组,把next[j]表示
为j的真前缀中能够自匹配的的前缀和后缀并且这个前缀的末尾字符和后缀的末尾字符不
同,这就是扩展kmp,让然用上面的文本串和模式串,在对对齐位置
0 0 0 0 0 0 0 1
0 0 0 1 下发成失配时,模式串会直接移动到
0 0 0 0 0 0 0 1
0 0 0 1,这样就避免了很多次不必要的匹配。
容易看出,在字符集规模越小的情况下扩展KMP的优势更加明显。
在原来的next数组的代码中只需改变一行就可以实现扩展KMP
void get_next (int *p) { int t; t = next[0] = -1; int j = 0; while (j < m) { if (t < 0 || p[j] == p[t]) {//匹配 j++, t++; next[j] = (p[j] != p[t] ? t : next[t]); } else //失配 t = next[t]; } }
int kmp () { get_next (P); int i = 0, j = 0; while (i < n && j < m) { if (j < 0 || T[i] == P[j]) { i++, j++; } else { j = next[j]; } if (j == m) { return i-j+1; } } return -1; }
HDU 1711:点击打开链接
最裸的KMP,直接借用上面的代码:
#include <cstring> #include <iostream> #include <cstdio> #include <algorithm> #include <vector> #include <cmath> using namespace std; #define maxn 1111111 int P[maxn], T[maxn]; int n, m; #define next Next int next[maxn]; void get_next (int *p) { int t; t = next[0] = -1; int j = 0; while (j < m) { if (t < 0 || p[j] == p[t]) {//匹配 j++, t++; next[j] = (p[j] != p[t] ? t : next[t]); } else //失配 t = next[t]; } } int kmp () { get_next (P); int i = 0, j = 0; while (i < n && j < m) { if (j < 0 || T[i] == P[j]) { i++, j++; } else { j = next[j]; } if (j == m) { return i-j+1; } } return -1; } int main () { int cas; cin >> cas; while (cas--) { scanf ("%d%d", &n, &m); for (int i = 0; i < n; i++) scanf ("%d", &T[i]); for (int i = 0; i < m; i++) scanf ("%d", &P[i]); printf ("%d\n", kmp ()); } return 0; }