算法:单模式串匹配

一、简介

单模式串匹配算法:是在一个模式串和一个主串之间进行匹配,也就是说在一个主串中查找另一个模式串

  1. BF:主串和模式串都不太长,时间复杂度为O(m*n)
  2. BM:模式串最好不要太长,预处理较重,实现较复杂,需要更多额外空间
  3. KMP:适合所有场景,整体实现起来也比BM简单,只需要一个next数组,但理解起来较难且统计分析下比BM慢
  4. sundy:算是对BM算法的改造,实现起来较简单

二、BF(Brute-Force-Match)

package StringMatch;

/*
    字符串匹配:暴力匹配算法
 */

public class VoliceMatch {
    public static int  volicematch(String a,String b){
        char[] s1 = a.toCharArray();
        char[] s2 = b.toCharArray();

        int m = s1.length;
        int n = s2.length;

        for (int i = 0; i <= m - n;i++){
            int k = 0;
            for (int j = 0;j < n;j++){
                if (s1[i+j] != s2[j]){
                    break;
                }else{
                    k++;
                }
            }
            if (k == n){
                return i;
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        System.out.println(volicematch("hellllo","lo"));
    }
}

三、BM(Boyer-Moore)

package StringMatch;

/*
    BM算法:
   思路:坏字符和好后缀原则
   eg:第一次匹配
        主串a:abcebmabcnbabceba,长度为m
      模式串b:abaeba,长度为n
   坏字符原则:从后往前匹配,把匹配不上的对应的主串的字符称为坏字符,若坏字符为倒数第一位,则可以往右直接滑动n位,若坏字符不确定,为了保证不移动过多而错过匹配,可以把坏字符对应的模式串下标记为s,
   把坏字符在模式串中的位置记为x,若坏字符在模式串中不存在,x为-1,若坏字符在模式串中有多个,取最后一个下标,往右滑动s-x位,第一次匹配时,坏字符为m,s为5,m在模式串中找不到,记为-1,往右移动6位.
   但仅仅靠坏字符匹配是不行的,比如主串是baabaaa,模式串为aaab,第一次匹配时,坏字符为b,s为0,x为b在模式串中位置,为3,往右移动了-3位,显然是不行的,为此引入的好后缀原则
   eg:第二次匹配
        主串a:abcebmabcnbabaeba,长度为m
      模式串b:      abaeba,长度为n
   好后缀原则:为了弥补坏字符可能出现往右偏移为负数的情况,如上述eg中第二次匹配时,坏字符为n,但是坏字符后的ba是能够匹配上的,我们称之为好后缀,假如好后缀在模式串中能找到另一个,开始下标记为x,往右偏移
   s-x+1位 = 3 - 1 + 1 = 3位,相当于如下所示,把另一个ba与好后缀ba对应上
        主串a:abcebmabcnbabaeba,长度为m
      模式串b:         abaeba,长度为n
   若好后缀在模式串中找不到另一个呢?如下所示,这时候判断好后缀的后缀子串与模式串的前缀子串能否匹配上,如好后缀为ba,后缀子串为a,a正好能跟模式串的第一位匹配上,这时往右移动j+2=5位
        主串a:abcebmabcnbabmeba,长度为m
      模式串b:      abmeba,长度为n
   上图所示例子好后缀只有两位,所以好后缀的后缀子串只能有1位,但好后缀的后缀子串有多位呢?此时还能移动j+2位吗?,如下所示,好后缀为ceba,且模式串中找不到ceba,此时eba的后缀子串为eba,ba,a,此时只有ba能与模式串的前缀子串匹配上,应该往
   右偏移j+2+1位,可以这么理解若好后缀的后缀子串只有1位能与模式串前缀子串匹配上,直接偏移j+2,好后缀的后缀子串增加一位,j+2相应加1
        主串a:abcebmabcebabaeba,长度为m
      模式串b:      baceba,长度为n
    用坏字符和好后缀都会得到一个往右的偏移量,比较哪个偏移量大即可
 */
public class BmMatch_zhu {
    private static final int SIZE = 256;
    public static int bmmatch_zhu(String str1,int m,String str2,int n){
        char[] a = str1.toCharArray();
        char[] b = str2.toCharArray();
        int[] bc = new int[SIZE];
        geneteBc(b,n,bc);
        int[] hz = new int[n];
        boolean[] qz = new boolean[n];
        genetehzqz(b,n,hz,qz);
        int i = 0;
        while (i <= m - n){
            int j;
            for (j = n - 1;j >= 0;j--){
                if (a[i+j] != b[j]){
                    break;
                }
            }
            if (j == -1){
                return i;
            }
            int x = j - bc[(int)a[i+j]];
            int y = 0;
            if (j < n - 1){
                y = move(j,n,hz,qz);
            }
            i = i + Math.max(x,y);
        }
        return -1;
    }

    private static void geneteBc(char[] b,int n,int[] bc){
        for (int i = 0; i < SIZE;i++){
            bc[i] = -1;
        }

        for  (int i = 0; i < n; i++){
            int ascii = (int)b[i];
            bc[ascii] = i;
        }
    }

    private static void genetehzqz(char[] b,int n,int[] hz,boolean[] qz){
        for (int i = 0; i < n; i++){
            hz[i] = -1;
            qz[i] = false;
        }

        for (int i = 0; i < n - 1;i++){
            int j = i;
            int k = 0;
            while (j >= 0 && b[j] == b[n-1-k]){
                j--;
                k++;
                hz[k] = j + 1;
            }
            if (j == -1){
                qz[k] = true;
            }
        }
    }

    private static int move(int j,int n,int[] hz,boolean[] qz){
        int k = n - j - 1;
        if (hz[k] != -1){
            return j - hz[k] + 1;
        }

        for (int r = j + 2; r <= n - 1;r++){
            if (qz[n - r] == true){
                return r;
            }
        }
        return n;
    }

    public static void main(String[] args) {
        System.out.println(bmmatch_zhu("hello",5,"lo",2));
    }
}

四、KMP(Knuth-Morris-Pratt)

package StringMatch;

/*
    思路:好前缀原则
    不同于bm算法的是,kmp算法是从前往后比较,这样找到坏字符后比较的就是好前缀,如
        主串a:ababaeabac,长度为m
      模式串b:ababacd,长度为n
    上述eg中坏字符为主串中的e,e前面的字符就称为好前缀,我们要在好前缀中找到最长可匹配前缀子串,如ababa的最长可匹配前缀子串为aba,找到最长可匹配前缀子串的最后一个字符下标位置,即2,比较b[2]和坏字符位置即可
        主串a:ababaeabac,长度为m
      模式串b:  ababacd,长度为n
 */
public class KmpMatch_zhu {

    public static int kmpmatch_zhu(String str1,int m,String str2,int n){
        char[] a = str1.toCharArray();
        char[] b = str2.toCharArray();
        int[] next = getNext(b,n);
        int j = 0;
        for (int i = 0; i < m;i++){
            while (j > 0 && a[i] != b[j]){
                j = next[j-1]+1;
            }

            if (a[i] == b[j]){
                j++;
            }

            if (j == n){
                return i - n + 1;
            }
        }
        return -1;
    }

    public static int[] getNext(char[] b,int n){
        int[] next = new int[n];
        next[0] = -1;
        int k = -1;
        for (int i = 1; i < n; i++){
            while(k != -1 && b[k+1] != b[i]){
                k = next[k];
            }

            if (b[k+1] == b[i]){
                k++;
            }

            next[i] = k;
        }
        return next;
    }

    public static void main(String[] args) {
        String str1 = "helllo";
        String str2 = "lo";
        System.out.println(kmpmatch_zhu(str1,str1.length(),str2,str2.length()));
    }
}

五、Sundy

package StringMatch;

/*
    思路:Sundy是对BM算法的简化
    BM算法是模式串从后往前匹配,Sundy算法和Kmp算法一样,从前往后匹配,当遇到不匹配的时候
    eg:
    主串:  substringsearching,长度为m
    模式串:search,长度为n
    比如说第二个字符就不匹配,找出主串中参与匹配的末尾字符的下一个字符,这里是i,判断i在模式串中存不存在,若不存在,直接把模式串往右偏移n+1位,如下所示
    substringsearching
           search
    若存在,如上所示,第二次匹配不上的是n,末尾的下一次字符是c,c在模式串是存在的,找到c在模式串对应的最后一个下标,即3,模式串往右偏移n-3 = 3位,如下所示,就匹配上了
     substringsearching
              search
     因为sundy算法没有好后缀原则,当遇到某种极端情况下是比较慢的,如
     baaaaaaa
     aaaaa
     这里第一次b是不匹配的,末尾的下一个字符为a,找到a在模式串的最后一个下标,即4,模式串往右偏移n-4 = 1位,若都是这种情况,可能每次都移动一位,时间复杂度就退化到和暴力匹配一样O(m*n)
 */
public class SundyMatch {
    private static final int SIZE = 256;

    public static int sundymathc(String str1,int m,String str2, int n){
        char[] a = str1.toCharArray();
        char[] b = str2.toCharArray();
        int[] move = getMove(b,n);
        int i = 0;
        while(i <= m - n){
            int j = 0;
            while (a[i+j] == b[j]){
                j++;
                if (j == n){
                    return i;
                }
            }
            i += move[(int)a[i+n]];
        }
        return -1;
    }

    private static int[] getMove(char[] b,int n){
        int[] move = new int[SIZE];
        //初始化
        for (int i = 0; i < SIZE; i++){
            move[i] = n + 1;
        }

        for (int i = 0; i < n; i++){
            int ascii = (int)b[i];
            move[ascii] = n - i;
        }
        return move;
    }

    public static void main(String[] args) {
        String a = "helllllllo";
        String b = "lo";
        System.out.println(sundymathc(a,a.length(),b,b.length()));
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我爱夜来香A

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值