KMP算法:假设i是指向主串的指针,KMP算法利用已经得到的“部分匹配”的结果将模式串向右“滑动”尽可能远的一段距离后,进行比较,而不需要回溯指针i。
一个简单的例子,反映了KMP算法的匹配过程:
假设主串为S=s1s2...sn,模式串为T=t1t2...tm(在S中寻找与T匹配的字符串)。
那么需要解决的问题就是:当匹配过程中产生失配时(si不等于tj),主串中第i个字符(i指针不回溯)应该与模式中哪个字符再比较?
假设此时应与模式中第k个字符比较,那么模式中前k-1个字符应当满足下列关系,且不存在k'>k满足下列关系:
t1t2...tk-1 = si-k+1si-k+2...si-1 ①
而已经知道的“部分匹配”的结果是
tj-k+1tj-k+2...tj-1 = si-k+1si-k+2...si-1 ②
由上面①②可以得到
t1t2...tk-1 = tj-k+1tj-k+2...tj-1 ③
反之,若模式串中存在满足式子③的两个子串,则当匹配过程中,主串的第i个字符与模式中第j个字符比较不等时,仅需将模式右滑至模式中第k个字符和主串的第i个字符对齐。
上面三个式子用图片展示更加明白
假设用next数组来存储k值,令next[j]=k,则next[j]表明当模式中第j个字符与主串中相应的字符失配时,在模式中需要重新和主串中该字符进行比较的位置。那么
1、当j=1时,next[j]=0,这个0相当于一个标记,如果与模式串的第一个字符失配了即t1与si不等时,那么下次应该比较t1和si+1。
2、当不存在相同的子串时(即找不出b=c),那么next[j]=1,表示下次应该比较t1和si。
3、其他情况next[j]=max{k|1<k<j 且有 b=c}
注意:next数组中的0值是一个标记,表示的是t1与si也不相等,下次需要比较t1和si+1
现在假设我们已经知道了next数组(虽然还不会求)
那么KMP算法的主体就出来了:
//S是主串,T是模式串,pos表示从主串的哪里开始匹配
//注意这里S和T的下标从0开始
//next数组的值也是表示下标,但是它的值的下标是从1开始的,0只是表示一种特殊情况
public static int KMP(String S,String T,int pos){
int i=pos,j=0;
next = getNext(T);
while(i<S.length()){
//si=tj继续比较si+1和tj+1,或者si与t0不等时继续比较si+1和t0
if(j==-1||S.charAt(i)==T.charAt(j)){
i++;j++;
}
else{
j=next[j]-1;//由于next中的值表示的下标是从1开始的,所以要减一
}
if(j==T.length())
return i-T.length();
}
return -1;
}
如何求next数组
next[j]=k,一般情况表示当si与tj失配时,下次应该比较si与tk,但如果k=0,则表示那么下次应该比较si+1与t1。
那么可以得到next[1]=0。(注意k的值代表模式串的下标,但是是从1开始的,k=0只是代表特殊情况)
假设next[j]=k,求next[j+1]
那么如果tk=tj,明显next[j+1]=k+1=next[j]+1;
如果tk不等于tj,则将求next[j+1]的值看成是字符串匹配的问题,模式串即是主串也是模式串,并且在sj与tk时失配,那么下一步应该将模式串滑动到next[k]。
假设next[k]=k',那么同理如果tk'=tj,next[j+1]=k'+1,否则继续右滑,直到k=0时,则表示不存在这样的k,那么next[j+1]=1,即应该从模式串的第一个开始匹配。
画图解释:
求next数组算法:
//注意这里next数组的下标和字符串的下标都是从0开始
//但是next数组中元素的值代表的下标是从1开始的
public static void getNext(String T){
next = new int[T.length()];
int i=0,j=0; //j表示next[i]的值,即k的值
next[0]=0; //当与第一个字符失配时,它的next值为0,表示应该比较T[0]和s[i+1]
while(i<T.length()-1){
//j=0表示特殊情况,即T[0]的next;由于k表示的下标是从1开始的,所以j-1才是字符的实际位置
//由于j初值为0,所以这里next[1]一定等于1,
//当next[1]赋值完之后,j=1,i=1,j正好是next[1]的值,而下一步求的是next[2]
if(j==0||T.charAt(j-1)==T.charAt(i)){
i++;j++;
next[i]=j;
}
else{
j = next[j-1];//next数组的下标是从0开始,k表示的下标从1开始,所以是j-1
}
}
}
next数组的修正
假设主串S="aaabaaaab",模式串T="aaaab,使用上面的算法求出的next数组的值应为 0 1 2 3 4
使用kmp算法匹配是在i=4,j=4时失配(下标从1开始),那么之后还会进行i=4、j=3,i=4、j=2,i=4、j=1这三次比较,而实际上模式串中第1~3个字符与第4个字符都相等,因此不需要在和主串的第4个字符比较,而是可以直接比较i=5、j=1。这就是说,若按上述定义得到next[j]=k,而模式中tj=tk,则当tj与si失配时,必定也与tk失配。所以这时不需要与tk比较,而是与t next[k]进行比较。由此可以得到修正算法。
public static void getNext(String T){
next = new int[T.length()];
int i=0,j=0; //j表示next[i]的值,即k的值
next[0]=0; //当与第一个字符失配时,它的next值为0,表示应该比较T[0]和s[i+1]
while(i<T.length()-1){
if(j==0||T.charAt(j-1)==T.charAt(i)){
i++;j++;
//若T[i]与T[j-1]相等,则直接比较Tnext[j-1]
if(T.charAt(i)==T.charAt(j-1))
next[i] = next[j-1]
else
next[i]=j;
}
else{
j = next[j-1];//next数组的下标是从0开始,k表示的下标从1开始,所以是j-1
}
}
}