一切都是最好的安排,但是我们为了实现最好的安排,每一步都要拼尽全力。
我们在一个母字符串中查找一个子字符串有很多方法。KMP是一种最常见的改进算法,它可以在匹配过程中失配的情况下,有效地多往后面跳几个字符,加快匹配速度。
"前缀"指除了最后一个字符以外,一个字符串的全部头部组合;
"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。
在KMP算法中有个数组,叫做前缀数组,也有的叫next数组,每一个子串有一个固定的next数组,它记录着字符串匹配过程中失配情况下可以向前多跳几个字符,当然它描述的也是子串的对称程度,程度越高,值越大,当然之前可能出现再匹配的机会就更大。
这个next数组的求法是KMP算法的关键,但不是很好理解,我在这里用通俗的话解释一下。
1.举一个Next数组的例子,注意next()方法里面求的就是这个数组。
下面来说明这个数组的求法:
2.当前面字符的前一个字符的对称程度为0的时候,只要将当前字符与子串第一个字符进行比较。这个很好理解啊,前面都是0,说明都不对称了,如果多加了一个字符,要对称的话最多是当前的和第一个对称。比如agcta这个里面t的是0,那么后面的a的对称程度只需要看它是不是等于第一个字符a了。
3.按照这个推理,我们就可以总结一个规律,不仅前面是0呀,如果前面一个字符的next值是1,那么我们就把当前字符与子串第二个字符进行比较,因为前面的是1,说明前面的字符已经和第一个相等了,如果这个又与第二个相等了,说明对称程度就是2了。有两个字符对称了。比如上面agctag,倒数第二个a的next是1,说明它和第一个a对称了,接着我们就把最后一个g与第二个g比较,又相等,自然对称成都就累加了,就是2了。
4.当然不可能会那么顺利让我们一直对称下去,如果遇到下一个不相等了,那么说明不能继承前面的对称性了,这种情况只能说明没有那么多对称了,但是不能说明一点对称性都没有,所以遇到这种情况就要重新来考虑,这个也是难点所在。
重点:
1.next数组的每个值的含义:在某个数组下标处,以数组下标处的字母结尾的字符串,前缀与后缀的最大重合长度。比如next[13]=7,就是在agctagcagctagc这个串中最大重合长度为7。
2. 如果想要计算next[14]的值,此时就需要寻求next[13]的帮助(知道前一位的最大重合长度,在前缀最大重合长度的基础上再多取一个字母即多取一个下标为7的字母,在后缀最大重合长度的基础上再多取一个字母即多取下标为14的字母,比较这两个字母是否相等,如果相等,则在next[13]的基础上+1,如果不相等见4)。
3.但是因为数组是从0开始的,取这个最大重合长度,自然就定位到最大重合的下一个字符,所以直接取next[13]=7的7这个值,去找s[7],此时实际是第8个字符。
4.(难点**) 如果不相等时也不直接去第一个字符处比较(确实不相等s[14]!=s[7]),相当于重复了在2的操作,在s[7]处,将a换成t(将s[7]换成了s[14])继续找s[7]处的最大重合长度(此时需要寻找next[6]的帮助),不断循环,直到无法找到重合子串,即到第一位与首字母a进行比较。
void makeNext(char s[],int next[])
{
int len = strlen(s);
next[0]=0; //初始化
for(int i=1,k=0;i0 && s[k]!=s[i]) //s[k]!=s[i]即完成s[i]和s[next[i-1]]的比较,
//不相等,则需要进入循环不断往前追溯。即步骤4
k=next[k-1];
//等价于 k=next[next[i-1]-1]
//等号右边的k起的只是下标的作用
if(s[k]==s[i])
k++; //相等就+1
next[i]=k; //赋值
}
}
至此,KMP算法的Next数组求完。
KMP全部代码如下
public class UseString {
//有了Next数组,现在来写字符串匹配算法吧。
public int KMP(String s1,String s2){
//求的模式串的Next数组
int i=0,j=0;
int[] next=next(s2);
for (int s:next
) {
System.out.println(s);
}
//Next数组输出完毕
for (;i0&&s1.charAt(i)!=s2.charAt(j)){
j=next[j-1];
}
if (s1.charAt(i)==s2.charAt(j)){
j++;
}
if (j==s2.length()){
return i-j+1;
}
}
return -1;
}
public int[] next(String s){
//获取字符数组
char[] chars=new char[s.length()];
for (int i=0;i0&&chars[i]!=chars[k]){
k=next[k-1];
}
if (chars[i]==chars[k]){
k++;
}
next[i]=k;
//判断循环结束的条件:为什么要这个循环呢?就是往next里面跳,不断的匹配最短的跳
// 1.k值等于0(到了最前面)
// 2.这个不匹配,要往前跳
}
return next;
}
public static void main(String[] args) {
String test1="adaagctagcagctagctg";
String test2="agctagcagctagctg";
UseString useString=new UseString();
System.out.println(useString.KMP(test1, test2));
}
}