Kmp算法
KMP算法
[TOC]
1.应用场景
字符串匹配问题。
demo:现在有主串S、模式串P,判断模式串P是否是主串S的子串。如果是,找出P在S中第一次出现时的下标。
2.从暴力求解到KMP算法
2.1暴力求解
思路:从左到右一个个匹配,如果这个过程中有某个字符不匹配,将子串向右移动一位,继续从左到右一一匹配。
(0)主串的0号元素和模式串的0号元素匹配成功
(1)主串的1号元素和模式串的1号元素匹配成功
(2)主串的2号元素和模式串的2号元素匹配成功
(3)主串的3号元素和模式串的3号元素匹配失败
(4)重新开始,主串从1号元素位置开始,从新检测与模式串P是否匹配
(5)重新开始,主串从2号元素位置开始,从新检测与模式串P是否匹配
(6)重新开始,主串从3号元素位置开始,从新检测与模式串P是否匹配,主串从3号元素和模式串的0号元素匹配成功
(7)主串从4号元素和模式串的1号元素匹配成功
(8)主串从4号元素和模式串的2号元素匹配成功
(9)主串从4号元素和模式串的3号元素匹配成功,确定模式串P是主串S的子串,P在S中,第一次出现的index=3
2.2KMP算法
暴力求解的存在的问题
当A和E不匹配后,模式串P后移一位(如下图所示),此时,模式串P的前三位(ABC)和主串的[1-3](BCA)肯定是不匹配的,无需浪费这几次比较,最好是直接让i不变,j==0
KMP思路
在需要重新开始匹配的时候,利用前面匹配的信息,保持i指针不变,通过修改j指针,让子串尽量地移动到有效的位置。
比如:
从第(3)步到第(4)步,能否保持i不变,依赖之前匹配的信息,通过把j移动到有效位置上,进行新的匹配
因此,整个KMP的重点就在于当某一个字符与主串不匹配时,我们应该知道j指针要移动到哪?
如下图:A和E不匹配了,我们要把j移动到哪?显然是第0位。为什么?因为前面有一个A相同可以用:
所有,移动j,再进行匹配,如下图
我们再来看另一种情况:如下图所示,C和B匹配失败,主串的AB[3,4]和模式串的AB[0,1]一致,所以i不动,j可以移动到2位置
观察可知,**匹配失败的时候,j变为k,j前面的的n个字符等于子串开头到k位置的n个字符的值**
即:P[0 ~ k-1] == P[j-k ~ j-1]
这时我们发现规律了,其实就是要求当前j之前的字符串,也就是ABCAB[0-j-1]它的首尾对称的长度最大长度,也就是PMT值。j向前移动n个字符的值=PMT值
PMT中的值是字符串的前缀集合与后缀集合的交集中最长元素的长度。
前缀
指除了最后一个字符以外,一个字符串的全部头部组合;
后缀
指除了第一个字符以外,一个字符串的全部尾部组合;
例如:
对于”aba”,它的前缀集合为{”a”, ”ab”},后缀集合为{”ba”, ”a”}。 两个集合的交集为{”a”},那么长度最长的元素字符串”a”的长度为1, 所以对于”aba”而言,它在PMT表中对应的值就是1。 对于字符串”ababa”,它的前缀集合为{”a”, ”ab”, ”aba”, ”abab”},它的后缀集合为{”baba”, ”aba”, ”ba”, ”a”}, 两个集合的交集为{”a”, ”aba”},其中最长的元素为”aba”的长度为3。 所以对于”ababa”而言,它在PMT表中对应的值就是3。
求Next数组
接下来核心就是求得模式串P每个下标元素对应的k值即可,因为在P的每一个位置都可能发生不匹配,我们要计算每一个位置j对应的k,所以用一个数组next来保存,next[j] = k,表示当S[i] != P[j]时,j应该变为k。
求NEXT数组的代码
public class Next {
public static int[] getNext(String ps) {
char[] p = ps.toCharArray();
int[] next = new int[p.length];
next[0] = -1; //
int j = 0;
int k = -1;
while (j < p.length - 1) {
if (k == -1 || p[j] == p[k]) {
next[++j] = ++k;
} else {
k = next[k];
}
}
return next;
}
}
两种主要情况
if (k == -1 || p[j] == p[k]) { 第一种p[j] == p[k]
next[++j] = ++k;
} else { 第二种p[j] != p[k]
k = next[k];
}
第一种p[j] == p[k]
p[j] == p[k]时,有next[++j] = ++k;
第二种p[j] != p[k]
KMP代码
public class Kmp {
public static int KMP(String ts, String ps) {
char[] t = ts.toCharArray();
char[] p = ps.toCharArray();
int i = 0; // 主串的位置
int j = 0; // 模式串的位置
int[] next = getNext(ps);
while (i < t.length && j < p.length) {
if (j == -1 || t[i] == p[j]) { // 当j为-1时,要移动的是i,当然j也要归0
i++;
j++;
} else {
// i不需要回溯了
// i = i - j + 1;
j = next[j]; // j回到指定位置
}
}
if (j == p.length) {
return i - j;
} else {
return -1;
}
}
}