字符串搜索算法kmp与Boyer-Moore,java实现

最近看《大话数据结构》,在串这种数据结构里面提到了kmp算法,在网上搜了一下又发现了更好的Boyer-Moore算法。大笑这里整理一下两种算法。(此篇文章主要用于记忆)

在字符串的匹配过程中大家很容易想到从首字符一个一个的去比较,

最快的情况:test(abcdefg),pattern(abc),这里匹配了三次就找到了,时间复杂度O{1}.

最慢:test(0000001),pattern(001),每一次前面两个0都要比较一下,比较了(7-3+1)*3=15次。时间复杂度为O{(n-m+1)*m}.

显然这样的暴力匹配是很费时的。

下面来介绍一下这两种算法得思想

kmp:e于c不等,在已经匹配的字符中找到相同的前缀(ab)和后缀(ab),这样直接用前缀的后一个字符和模板中的下一个字符比较(也就是为了不使模板中的字符发生回溯)。


这里移动的距离是:已经匹配的长度(6)-相同的前后缀长度(2)=4

Boyer-Moore:这种算法直接换了一种思考模式,不再从前往后一个一个匹配,直接匹配pattern的最后一个字符,也就是从后往前匹配。不匹配则直接跳到下一个位置,匹配则往前移动到不匹配的字符,并用坏字符和好后缀算法得出移动位置。(详情见参考文章)


这里直接给出代码:

/**
     * 用于生成部分匹配表
     * 例:ABCDABD-->[0,0,0,0,1,2,0]
     * @param sub
     * @return
     */
    private static int[] kmpnext(String sub) {
        int[] n = new int[sub.length()];
        n[0]=0;
        int x = 0;
        for (int i = 1; i < sub.length(); i++) {
            while (x > 0 && sub.charAt(i) != sub.charAt(x)) {
                x = n[x - 1];
            }


            if (sub.charAt(i) == sub.charAt(x)) {
                x++;
            }


            n[i] = x;
        }
        return n;
    }
    
    /**
     * kmp匹配法
     * @param str
     * @param sub
     * @return
     */
    public static int kmp(String str, String sub) {
        if(str == null || sub == null || str.length() == 0 || sub.length() == 0){
            throw new IllegalArgumentException("str或者sub不能为空");
        }
        List returnList=new ArrayList();
        int j = 0;
        int[] n = kmpnext(sub);
        for (int i = 0; i < str.length(); i++) {
            while(j > 0 && str.charAt(i) != sub.charAt(j)){
            //j=j-(j-n[j-1])=n[j-1](移动位数 = 已匹配的字符数 - 对应的部分匹配值)
            /*
            * 匹配字符:abddcab,目标字符:abddcacabddcab。当b与c不等时,不需要再用a与bddca比较,直接可得出a=a,
            * 再接着比较两个a后面的字符
            * */
                j = n[j - 1];
            }


            if(str.charAt(i) == sub.charAt(j)){
                j++;
            }


            if(sub.length() == j){
                int index = i - j + 2;
                //j=0;
                returnList.add(index);
                return index;
            }
        }


        return -1;
    }
    /**
     * 好后缀:如果程序匹配了一个好后缀, 并且在匹配字符串中还有另外一个相同的后缀或后缀的部分, 那把下一个后缀或部分移动到当前后缀位置。
     * @author sunzhe
     * @param len,pattern,
     * */
    public static int[] moveByGood(String pattern,int len){
    int i,j;
    int[] suff = new int[len];
    int[] returnArray = new int[len];
    // 计算后缀数组
    suff = suffix(pattern,len);
    //Case3:如果完全不存在和好后缀匹配的子串,则右移整个模式串。
    for(i = 0;i < len; i++){
    returnArray[i] = len;
    }
    //Case2:如果不存在和好后缀完全匹配的子串,则在好后缀中找到具有如下特征的最长子串,使得P[m-s…m]=P[0…s]。
   
    for(i = len-1; i >= 0; i--){
    if(suff[i] == i + 1)
           {
               for(j = 0; j <= len - 1 - i - 1; j++)
               {
                   if(returnArray[j] == len)
                    returnArray[j] = len - 1 - i;
               }
           }
    }
        //Case1:模式串中有子串和好后缀完全匹配,则将最靠右的那个子串移动到好后缀的位置继续进行匹配。
    for(i = 0; i < len - 1; i++){
    returnArray[len - 1 - suff[i]] = len - 1 - i;
    }
    return returnArray;
    }
    /**
     * 后缀数组(匹配字符部分和全部的相同个数)
     * @author
     * @param len,pattern
     * */
    public static int[] suffix(String pattern,int len){
    int[] returnArray = new int[len];
    int f,i;
    returnArray[len-1]=len;
    for(i=len-2;i>=0;i--){
   
    f=i;
    while(f>=0 && pattern.charAt(f)==pattern.charAt(len-1-i+f)) 
    f--;
    returnArray[i]=i-f;
    }
   
    return returnArray;
    }
    
    /**
     * 坏字符
     * @author sunzhe
     * @param c,pattern,j
     * */
    public static int moveByBad(int j,String pattern,char c){
    boolean flag=false;
    for(int i=0;i<pattern.length();i++){
    if(pattern.charAt(i)==c){
    flag=true;
    }
   
    }
    if(!flag){
    return j-(-1);
    }else{
    return j-last(pattern,c);
    }
    }
    /**
     * @author sunzhe
     * 搜索词中的上一次出现位置
     * */
    public static int last(String pattern,char c){
    for(int i=0;i<pattern.length();i++){
    if(pattern.charAt(i)==c && pattern.charAt(i+1)!=c){
    return i;
    }
    }
    return -1;
    }
    
    /**
     * @author sunzhe
     * Boyer-Moore算法
     * */
    public static int bM(String pattern, String test){
    int j=pattern.length()-1;
    int i=0;
    i=j;
    int[] good=moveByGood(pattern, pattern.length());
    while(i<test.length()){
    if(test.charAt(i)==pattern.charAt(j)){
    i--;
    j--;
    }else{
    char c=test.charAt(i);
   
    i=i+Math.max(moveByBad(j, pattern, c), good[j]);
    j=pattern.length()-1;
    }
    if(j==0){
    return i+1;
    }
    }
    return -1;
    }

注:


好后缀算法中先把整个数组赋值,在根据情况(if条件)修改数组中的值。这里很好体现了算法转为程序的思维。

参考文章:http://www.ruanyifeng.com/blog/2013/05/boyer-moore_string_search_algorithm.html

                    http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html

                    http://blog.jobbole.com/52830/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值