KMP算法用途
KMP算法主要的用途就是字符串匹配,例如查找字符串"ab"(目标字符串)在字符串"abc"(待查找字符串)中出现的位置。也就是查找字符串"abc"中是否包含字符串"ab",如果包含,返回包含的起始位置。
先看下Java实现
public int[] next(String str){
int[] next = new int[str.length()];
int j = 0;
int k =-1;
next[j] = k;
while (j < next.length - 1){
if(k == -1 || str.charAt(j) == str.charAt(k)){
next[++j] = ++k;
}else {
k = next[k];
}
}
return next;
}
public int kmp(String str1, String str2){
int[] next = next(str2);
int i = 0;
int j = 0;
while(i < str1.length()){
if(j == -1 || str1.charAt(i) == str2.charAt(j)){
i++;
j++;
}else {
j = next[j];
}
if(j == str2.length()){
return i - j;
}
}
return -1;
}
其中最重要的是next()函数
KMP算法的核心,是一个被称为部分匹配表PMT(Partial Match Table)的数组,也就是next()函数的返回值。
那什么是PMT呢?例如:对于字符串“abababca”,它的PMT如下表所示:
待匹配的模式字符串有8个字符,那么PMT就会有8个值。
解释PMT之前,先了解一下字符串的前缀和后缀。如果字符串A和B,存在A=BS,其中S是任意的非空字符串,那就称B为A的前缀。例如,”Harry”的前缀包括{”H”, ”Ha”, ”Har”, ”Harr”},我们把所有前缀组成的集合,称为字符串的前缀集合。同样可以定义后缀A=SB, 其中S是任意的非空字符串,那就称B为A的后缀,例如,”Potter”的后缀包括{”otter”, ”tter”, ”ter”, ”er”, ”r”},然后把所有后缀组成的集合,称为字符串的后缀集合。要注意的是,字符串本身并不是自己的后缀。
链接:https://www.zhihu.com/question/21923021/answer/281346746
来源:知乎
PMT中的值就是字符串的前缀集合与后缀集合的交集中最长元素的长度。例如,对于”aba”,它的前缀集合为{”a”, ”ab”},后缀 集合为{”ba”, ”a”}。两个集合的交集为{”a”},那么长度最长的元素就是字符串”a”了,长 度为1,所以对于”aba”而言,它在PMT表中对应的值就是1。
next()函数实现我们等会解释,先看看如何使用这个PMT表加速查找
如图 1.12 所示,要在主字符串"ababababca"中查找模式字符串"abababca"。如果在 j 处字符不匹配,那么模式字符串(0, j-1) 的位置是和主串(i-j, i-1)的位置是匹配的。由前边所说的模式字符串 PMT 的性质可知,模式字符串从 0 到 j−1 ,在这个例子中就是”ababab”,其前缀集合与后缀集合的交集的最长元素为”abab”, 长度为4,所以就可以断言,主字符串中i指针之前的 4 位一定与模式字符串的第0位至第 4 位是相同的,即长度为 4 的后缀与前缀相同。这样一来,我们就可以将这些字符段的比较省略掉。具体的做法是,保持i指针不动,然后将j指针指向模式字符串的PMT[j −1]位即可。
依据上面的思路,KMP算法如下
我们看到如果是在 j 位 失配,那么影响 j 指针回溯的位置的其实是第 j −1 位的 PMT 值,所以为了编程的方便, 我们不直接使用PMT数组,而是将PMT数组向后偏移一位。我们把新得到的这个数组称为next数组。我们令next[0] = -1;只是为了编程方便。
public int kmp(String str1, String str2){
int[] next = next(str2);
int i = 0;
int j = 0;
while(i < str1.length()){
if(j == -1 || str1.charAt(i) == str2.charAt(j)){
i++;
j++;
}else {
j = next[j];
}
if(j == str2.length()){
return i - j;
}
}
return -1;
}
next()实现原理解释
求next数组的过程完全可以看成字符串匹配的过程,即以模式字符串为主字符串,以模式字符串的前缀为目标字符串,一旦字符串匹配成功,那么当前的next值就是匹配成功的字符串的长度。
具体来说,就是从模式字符串的第一位(注意,不包括第0位)开始对自身进行匹配运算。 在任一位置,能匹配的最长长度就是当前位置的next值。如下图所示。