BM算法代码详解

BM算法

构建坏字符规则

    private static int[] generateBadChar(String s){
        int[] barChar = new int[256];
        Arrays.fill(barChar, -1);
        for (int i = 0; i < s.length(); i++){
            barChar[s.charAt(i)] = i;
        }
        return barChar;
    }

字符这里是采用字符的ASCII码记录字符在模式串中出现的最后位置,如果在遍历模式串的过程中,后面重复的字符会覆盖掉原来记录过的下标值。这样在barChar的数组中记录的是所有字符出现的最后的下标位置

这里的初始值-1也是有意义的,-1表示对应的字符没有在模式串出现过。这是利用坏字符规则,主串i移动可以直接移动到出现坏字符的下一位

比如说:

这里主串和模式串匹配时,出现坏字符SE不匹配,而坏字符S在模式串中没有出现,在barChar数组中对应记录的值为-1,这是i=0, j=6的,此时主串就可以移动i = j - barChar[s.char(i+j)]位,即6-(-1) = 7

在这里插入图片描述

构建好后缀规则

    private static void generateGoodSuffix(String pattern, int[] suffix, boolean[] prefix){
        Arrays.fill(suffix, -1);
        Arrays.fill(prefix, false);
        for (int i = 0; i < pattern.length() - 1; i++) {
            int j = i, k = 0;
            while (j >= 0 && pattern.charAt(j) == pattern.charAt(pattern.length() - 1 - k)) {
                suffix[++k] = j--;
            }
            if (j == -1){
                prefix[k] = true;
            }
        }
    }

首先我们要对两个数组进行说明:

suffix数组记录的是模式串中对应后缀长度子串的末尾索引,比如说abcabc,它的suffix数组中记录的值为[-1, 2, 1, 0, -1, -1],其中suffix[2] = 1表示满足后缀长度为2的后缀字串末尾索引是1。即现在是末尾的bc,长度为2。而在模式串后面又出现了bc了,那bc的末尾索引是1,即是b的下标。注意一下,我说的后面是从右向左看这个模式串。

总结以下就是**suffix数组的下标表示的是能和模式串前面能够匹配的长度,而记录的值是满足条件的后缀字串的末尾下标**。

prefix数组,记录了模式串中每个位置的前缀字串是否是模式串的前缀。前缀子串是指一个字符串的开头部分。还是拿abcabc举例,它的prefix数组记录的值是[false, false, false, true, false, false]prefix[3] = true表示表示模式串中长度为 3 的前缀子串是模式串的前缀。即abcabc能够匹配。下标也可以理解为能够匹配的长度。只有当前缀子串的开头部分和结尾部分重合时,才会将对应的 prefix 数组元素设置为 true。

suffix数组不同的是,suffix数组是匹配是可以在模式串中间出现重复的部分,而prefix必须是要到下标为 0 的位置

代码中要需要注意的是i < pattern.length() - 1 i不能等于pattern.length() - 1。如果等于了相当于jpattern.length() - 1 - k从同一个位置开始,自己和自己匹配,破坏了后缀子串的条件,会导致整个记录更新,错误记录。

根据好后缀规则确定滑动位数

    private static int moveByGoodSuffix(int j, int[] suffix, boolean[] prefix) {
        int k = suffix.length - 1 - j;
        if (suffix[k] != -1){
            return j - suffix[k] + 1;
        }
        for (int r = j + 2; r < suffix.length; r++) {
            if (prefix[suffix.length - r]) {
                return r;
            }
        }
        return suffix.length;
    }

这里的j表示的是遍历到坏字符的位置,k则是前面满足匹配的字符串长度。根据k这个长度去查看模式串后面是否有满足长度的后缀字符串。如果suffix[k] != -1表示有,则计算返回移动的步数,j是坏字符的位置,suffix[k]是能够匹配后缀字符串末尾索引,两者相减即两个索引之间相差长度,再加 1 就是最后需要移动的步数。

如上i = 2, j = 2,在主串i+j和模式串j匹配错误了,利用好后缀的原则,此时的k = 5,没有好后缀,利用prefix数组找到最短的前缀,此时prefix[3] = true,移动结果为 8 -

说明一下r表示的是可移动的步数,表示模式串向右滑动r位。之所以从j+2开始,是我们知道[j+1, length-1]的好后缀已经不匹配了,因为j是坏字符,从j+1length-1的位置如果存在好的后缀在suffix的条件中就会进行处理了。所以走prefix条件一定是从j+2开始。length-r就是能够进行匹配的长度。

如果还没有后缀字符串就整体移动。

BM算法

    public static int strBM(String text, String pattern){
        // 坏字符规则
        int[] badChar = generateBadChar(pattern);
        // 好后缀规则
        int[] suffix = new int[pattern.length()];
        boolean[] prefix = new boolean[pattern.length()];
        generateGoodSuffix(pattern, suffix, prefix);
        int i = 0;
        while (i <= text.length() - pattern.length()) {
            int j = pattern.length() - 1;
            while (j >= 0 && text.charAt(i + j) == pattern.charAt(j)) {
                j--;
            }
            if (j < 0) return i;
            int moveLen1 = j - badChar[text.charAt(i + j)];
            int moveLen2 = 0;
            if (j < pattern.length() - 1) {
                moveLen2 = moveByGoodSuffix(j, suffix, prefix);
            }
            i += Math.max(moveLen1, moveLen2);
        }
        return -1;
    }

首先构建好坏字符规则和好后缀规则。从模式串从后向前进行匹配,这里j是模式串的指针,i+j是主串上的指针。当j比0小,匹配成功,此时i指向的位置就是能够匹配的字符串的第一个字符下标,也因此得出i的范围[0, text.length()-pattern,length()]

moveLen1是根据坏字符规则得到的滑动距离,moveLen2是根据好后缀得到的滑动距离,取二者的最大滑动距离,算法效率高。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值