赞
踩
一切都是最好的安排,但是我们为了实现最好的安排,每一步都要拼尽全力。
我们在一个母字符串中查找一个子字符串有很多方法。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;i<len;i++)
- {
- //这个while是最关键的部分,k就是前一位的最大重复个数,即next[i-1]
- while(k>0 && 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 (;i<s1.length()&&j<s2.length();i++){
- //回退到相等的地方。这个j和前面next数组的K有什么关系?
- //在求next数组的时候,k是前一位匹配的最大重合长度,chars[k]!=chars[i]是比较在前一次的最长前缀的新增后一位,和加入新的元素的最长后缀的新添加一位是否一样,不一样则需要回退。这个k和i都是一个字符串的指针。
- //这里的j是正在和主串匹配的长度,能匹配到j,说明前j-1个元素和主串能进行匹配,当这一位不满足时,需要找前一位的最长重合长度。
- while (j>0&&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;i<s.length();i++){
- chars[i]=s.charAt(i);
- }
- //得到next数组
- int[] next=new int[s.length()];
- next[0]=0;
- //k是前一位的最大重合个数
- for (int i=1,k=0;i<next.length;i++){
- //这块有些反逻辑,新手想不出来的。为什么这个while循环要在前面呢?
- //和判断条件有关系,
- //你是从第一个数往后想,第一个匹配然后第二个呢?
- //但是从整体上来讲,你应该是这样的:
- //找出这个数的前一个的最大重合子串,看最大重合子串,和这个数是否相等
- //如果不相等则回退,一直回退到相等或者没有相等的即此时k=0,跳出这个循环
- while (k>0&&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));
-
-
- }
- }
-
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。