kmp学习笔记(模板)
介绍
Knuth-Morris-Pratt字符串查找算法(简称为KMP算法)
用于字符串的模式匹配。相比朴素算法,它的时间复杂度为O(n + m),其中n是文本串长度,m是模式串长度。
原理
kmp通过计算模式串的next数组作为辅助来减少朴素算法中重复的比对。
next数组代表模式串的最长相同真前后缀。例如:在模式串s =“ABABCD”中,它的next数组为next=[-1 0 0 1 2 0 0]。其中next[0]为-1为的是保证完全失配时不陷入死循环。
假设有文本串t="ABADDABABD"。开始以位置0为起点,在匹配到t[0:2]="ABA"都相等;但是匹配到t[3] = 'D' != s[3] = 'C',这时不需要从位置1重新开始。因为由next[3]=1知道,s串位置3之前最长相同真前后缀为1,即s[0:0] == s[2:2]。因此可以直接从文本串的位置3开始和模式串的位置1匹配,即next[3]位置。
如何构造next数组?假设包括位置 i 前的next数组已经构建完成,设j = next[i]。如果s[i + 1] == s[j + 1],那么显然next[i + 1] = j + 1;否则,由于s[0 : next[j]]存在真前缀和s[0 : i - 1]真后缀相等,故令j = next[j],如果s[i + 1] == s[j + 1],那么next[i + 1] = j + 1,否则重复上述过程,直到j = next[0] = -1代表不存在相同的真前后缀,那么next[i + 1] = 0。
时间复杂度O(n)
实现
板子都很好写
//求next
int nt[N]; //next
void getnext(char t[]) {
int i = 0, j = -1;
nt[i] = j;
while(t[i]) {
if(j == -1 || t[i] == t[j]) {
i++;
j++;
nt[i] = j;
} else {
j = nt[j];
}
}
}
void search(char s[], char t[]) { //s文本串,t模式串
int i = 0;
int j = 0;
while(s[i]) {
if(j == -1 || s[i] == t[j]) {
i++;
j++;
if(!t[j]) { //找到了
//处理
j = nt[j]; //继续找。如果只找一次可以直接break
}
} else {
j = nt[j];
}
}
}
应用
除了匹配模式串,还可以找循环节,找最长相同前后缀等等。具体看题。
扩展kmp
设文本串为s(长度为n),模式串为t(长度为m)。扩展kmp作用是求extend数组。其中extent[i]代表t和s[i : n-1]的最长相同前缀。
由于当存在extend[i] == m时,就起到kmp算法的功能,故又称扩展kmp。
求extend数组需要求next数组作为辅助。这里next[i]代表t和t[i : m - 1]的最长相同前缀。假设next数组已经求出,下面求extend数组。
设p为匹配过程中能达到的最右边界。设a为p对应位置,即extend[a] = p - a。假设包括i - 1前的extend数组都已经求出。那么首先,i的位置必定位于[a,p]区间之内。那么i位置对应在next上的位置为i - a。有两种情况:
- 如果i + next[i - a]小于p,那么可得extend[i] = next[i - a];
- 如果i + next[i - a] 大于等于p ,那么在直接在t[p - i]的基础上扩展就好了。注意是p - i而不是p - a,因为是在前缀的基础上扩展。然后更新p和a。
求next数组其实就是t自己对自己求extend数组的过程。
板子很好写
int extend[N];
int nt[N]; //next
void getnext(char t[]) {
int a = 0, p = 0;
int len = strlen(t);
nt[a] = len;
for(int i = 1; i < len; i++) {
if(i >= p || i + nt[i - a] >= p) {
if(i >= p) p = i;
while(p < len && t[p] == t[p - i]) p++;
nt[i] = p - i;
a = i;
} else {
nt[i] = nt[i - a];
}
}
}
void search(char *s, char *t) {
int a = 0, p = 0;
int n = strlen(s);
int m = strlen(t);
for(int i = 0; i < n; i++) {
if(i >= p || i + nt[i - a] > p) { // i >= p 的作用:举个典型例子,S 和 T 无一字符相同
if(i >= p) p = i;
while(p < n && p - i < m && s[p] == t[p - i]) p++;
extend[i] = p - i;
a = i;
} else {
extend[i] = nt[i - a];
}
}
}
相关题目
kuangbin kmp专题
扩展kmp:
Clairewd’s message
Period II
Count the string