您的位置:首页 > 其它

KMP学习笔记

2016-05-08 22:45 302 查看
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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: