一、简述
KMP算法是对BF(Brute Force)算法做了很大改进的模式匹配算法,可以将时间复杂度从O(m*n)降低到O(m+n)。
二、KMP算法的思想
设主串s="acabaabaabcacaabc",模式t="abaabcac"
(一)分析BF算法
BF算法匹配过程如下:
第1趟
第2趟
第3趟
第4趟
第5趟
第6趟
分析BF算法的执行过程,发现造成BF算法慢的主要原因是回溯,在某一趟的匹配过程中,一旦匹配失败,主串要回到本趟开始开始匹配字符的下一个字符,模式串要回到第一个字符,而这些回溯有时候是不必要的。对于第3趟来说,因为s8 != t6,所以才需要进行第4趟的匹配,但是第4趟的匹配是不必要的,因为在第3趟中,有s4 = t2,而t1 != t2,所以,s4 != t1。 同理第5趟匹配也是不必要的,因此可以从第3趟直接到第6趟。进一步分析第6趟,s6和t1的比较是多余的,s7和t2的比较也是多余的,因为第3趟中有,s3~s7=t1~t5,则s6=t4,s7=t5,又因为t1t2=t4t5,必有s6s7=t1t2,因此第6趟的比较可以从s8和t3开始,也就是说,在第3趟的比较中,s8和t6匹配失败后,i指针不动,将模式串向右“滑动”,用t3对准s8继续匹配,依次类推。用这样的处理方法,指针i不需要进行回溯。
综上所述,当si和tj匹配失败后,我们希望指针i不回溯,而是让模式串向右滑动至某个位置k,使得si和tk继续比较。要满足这个假设,则需要满足:
t1t2...tk-1 = si-k+1si-k+2...si-1
等号左边是模式串的前k-1个字符,等号右边是主串si之前的k-1个字符。
又因为k<j,则:
t1t2...tk-1 = tj-k+1tj-k+2...tj-1 (1.1)
结论:
在si和tj匹配失败后,如果在模式串中存在满足式1.1的字串,即模式串的前k-1个字符和模式串tj前的k-1个字符相等时,模式串就可以滑动到tk和si对准继续比较。
(二)next函数
模式串的每一个tj都对应一个k值,由式1.1可知,这个k值只依赖与模式串本身,与主串无关。用函数next(j)来表示tj对应的k值。由以上分析可知,next(j)函数的性质如下:
1)next(j)是一个整数,且0<=next(j)<j;
2) 为了使得t的右移不丢失任何匹配成功的可能,当有多个满足式1.1的k值时,应取最大的,这样向右滑动的距离最短,向右滑动的字符为j-next(j)个;
3)当不存在满足式1.1的字串时,则k=1;即模式串t1和si匹配,此时模式串滑动最远,为j-1个字符。
next函数定义如下:
(三)KMP算法
假设模式串t字符对应的每一个k值都计算好,并且存放在next数组中。指针i和指针j分别指向主串和模式串,依次比较,当si=tj时,指针i和指针j加1,继续匹配;当si != tj时,指针i不变,j=next[j],继续匹配;当j=0时,指针i和指针j加1,即模式串退回至第一个字符,主串移动到下一个字符继续匹配。
三、KMP算法代码实现
package DataStructureandAlgorithm;
/**
* @author
* @date 2020/1/11 21:07
*/
public class KMP {
/**
* @description next[j]值的含义:
* 1、表示主串与模式串匹配失败后,
* 模式串应该以索引next[j]字符的值与主串匹配失败的索引的值继续比较
* 2、表示索引j字符前的最大前缀后缀的后一个字符所在的索引
* */
public int[] getNext(String t){
int k=-1,j=0;
int len = t.length();
int[] next = new int[len];
//模式串的第一个字符next值为-1
next[0] = -1;
while(j<len-1){
if(k == -1 || t.charAt(k) == t.charAt(j)){
k++;
j++;
next[j] = k;
}else{
k = next[k];
}
}
return next;
}
//返回模式串在主串的索引
public int getIndex(String s,String t,int next[]){
int i = 0,j = 0;
int sLen = s.length();
int tLen = t.length();
while(i<sLen && j<tLen){
if(next[j] == -1 || s.charAt(i) == t.charAt(j)){
i++;
j++;
}else{
j = next[j];
}
}
if(j == tLen){
return i-tLen;
}else{
return -1;
}
}
public static void main(String[] args){
KMP kmp = new KMP();
//s = acabaabaabcacaabc
//t = abaabcac
String s = "aabcbabcaabcaabab";
String t = "abcaababc";
int next[] = kmp.getNext(t);
System.out.println(kmp.getIndex(s,t,next));
}
}