BF算法和KMP算法

BF算法

又叫朴素算法,男朋友算法,时间复杂度为O(mn)相比KMP算法比较简单,举个例子,对于给定的主字符串“ababbcabcdabcde”和子串“abcd”;我们用i和j来分别遍历两个字符串,比较两个i,j 对应字符串位置的元素是否相等,如果相等则i++,j++去比较下一个元素,如果i和j 对应位置的元素不相等,则j需要退回到子字符串的0号下标位置,而i需要退回到之前的位置+1,这个位置下标也就是i-j+1的位置,然后继续比较,相等i++,j++,不等j需要退回到子字符串的0号下标位置,i需要退回到i-j+1的位置,重复只到找到为止,找到返回i-j,找不到返回-1

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

package com.xagy.chuan;
/*
 * 朴素算法
*/

public class Bf {
    /**
     * BF算法
     * @param str 字符串,主串
     * @param sub 字符串,子串
     * @param pos 从主串开始查找的位子
     */
    public static int BF(String str,String sub,int pos) {
        if(pos < 0 || pos > str.length()) {//判断pos合法性 
             return -1;
        }
        int i = pos;//开始查找
        int j = 0;
        //遍历主串和子串
        while(i < str.length() && j<sub.length() || sub.length() > str.length()) {
            //判断对应下标元素是否相同
            if(str.charAt(i) == sub.charAt(j)) {
                i++;
                j++;
            }else {
                i = i-j+1;
                j = 0;
            }
        }
        if(j >= sub.length()) {//已经找到了子串在主串的位置
            return i-j;
        }
        return -1;
    }
    public static void main(String[] args) {
        String str = "ababbcabcdabcde";
        String sub = "abcd";
        System.out.println( BF(str,sub,0));
    }

}

运行结果:

6

KMP算法

KMP算法的时间复杂度为O(m+n),所以它相比于BF算法更为高效,它的实现过程与BF算法唯一不一样的地方在于,KMP算法的主串i并不会回退,并且j也不会回退到0号位置:

这里写图片描述
这个时候主串i并不需要回退,并且j也不会回退到0号位置,而是回退到一个位置k,

这里写图片描述

至于子串中位置k的查找则需要用到next数组,

这里写图片描述

到了c的位置,按next数组返回,next数组为2,即返回到2号下标位置,也就是上面我们推得k位置,所以寻找k位置可以用next数组最方便,next数组的求法是一边从0号下标位置开始往后找,一边从当前j-i号位置开始从后往前找,找到最长有几个元素相等next数组就是几:

如:

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述
而这种情况下,
这时候,对应的 next 数组:-1,0,0,0,1,2 next[2] 对应的就是 c.
假定主串 S 子串 P:
P0…PK-1 = Px…Pj-1 则有:P0…PK-1 = Pj-k…Pj-1
接下来又有一个问题:每一次不成功,肯定会有对应的一个 K 值。那么如何保存这些值?怎么求这些值?那这就是KMP 的精髓。在这里就会出现新的东西,那就是 next 数组;也就是用 next[j] = k;来表示,不同的 j 来对应一个 K 值,这个 K 就是你将来要移动的 j 要移动的位置。而 K 的值是这样求的:
1、规则:找到匹配成功部分的两个相等的真子串(不包含本身),一个以下标 0 开始,另一个以 j-1 下标结尾。
2、不管什么数据 next[0] = -1;next[1] = 0;在这里,我们以下标来开始,而说到的第几个第几个是从 1 开始;
-1 的理由:当主串为–”defrdes” 子串为:”abc” 一开始就匹配失败。
0 的理由:当子串在 1 号下标匹配,此时为 0;

  1. 得到 next 数组:如果我们能够通过 next[i]的值,通过一系列转换得到 next[i+1]得值,那么我们就能够实现这部分。
    那该怎么做呢?首先假设:next[i] = k 成立,呢么,就有这个式子成立:P0…Pk-1 = Px…Pi-1;得到:P0…Pk-1 = Pi-k..Pi-1;
    到这一步:我们再假设如果 Pk = Pi;我们可以得到 P0…Pk = Pi-k..Pi;那这个就是 next[i+1] = k+1;那么:Pk != Pi 呢?首先
    在不相等的时候,看这个例子:abcababcabc

这里写图片描述

next 数组的优化,即如何得到 nextval 数组:
有如下串:aaaaaaaab,他的 next 数组是-1,0,1,2,3,4,5,6,7.而修正后的数组 nextval 是:

-1,-1,-1,-1,-1,-1,-1,-1,7。为什么出现修正后的数组,假设在 5 号处失败了,那退一步还是 a,还是相等,接着退还是 a。

这里写图片描述

代码:

package com.xagy.chuan;
//找到子串中的两个真子串,一个以0号下标开始,一个以j-1下标为止
public class Kmp {
    /**
     * KMP核心算法
     * @param str 
     * @param sub
     * @return
     */
    public static int KMP(String str,String sub)
    {
        //先判断是否符合规范
        if(str.length()<sub.length())
        {
            return -1;
        }
        //机获得Next数组
        int [] next = getNext(sub);
        //sub下标位置
        int j=0;
        //str的下标位置
        int i=0;
        //直到i,j超出下标
        while(i<str.length()&&j<sub.length())
        {
            //如果str和sub的当前元素相等,则i和j同时移动
            //j=-1为,如果在匹配第一个失败的时候,进行回退会回退到-1位置,数组会越界,所以直接向后移动
            if(j==-1||str.charAt(i)==sub.charAt(j))
            {
                i++;
                j++;
            }
            //否则j根据next数组进行回退,i不回退
            else
            {
                j = next[j];
            }
        }
        //如果j大于sub数组,则说明查找成功了,返回第一个找到的第一个下标
        if(j>=sub.length())
        {
            return i-j;
        }//如果没找到,返回-1
        else
        {
        return -1;
        }
    }
    /**
     * 求Next数组
     * @param sub 要匹配的字符串
     * @return Next数组
     */
    public static int[] getNext(String sub)
    {   //创建一个数组
        int[] next = new int[sub.length()];
        //初始化
        next[0] = -1;
        next[1] = 0;
        //当前要求next的位置
        int i = 2;
        //k位置
        int k =0;
        //循环直到最后一个节点
        while(i<sub.length()-1)
        {
            //如果k=-1或者两元素相等
            if(k==-1||sub.charAt(k)==sub.charAt(i-1))
            {
                //当前的next大小等于前一个next+1
                next[i++] = ++k; 
            }//如果不相等,进行回溯,寻找可以匹配的最小子串
            else
            {   
                k = next[k];
            }
        }
        //返回next数组
        return next;
    }
    public static void main(String[] args) {
        String str = "abcabbcabcaabdab";
        String sub = "abbcabca";
        System.out.println(KMP(str,sub));

    }

}

运行结果 :

3

猜你喜欢

转载自blog.csdn.net/QQ2899349953/article/details/80297763