字符串匹配算法(二)BM算法

算法简介

  • BM算法也就是Boyer Moore算法,它是一种非常高效的字符串匹配算法,是一种滑动算法。
  • 什么是滑动?
    下面例子中,主串中的c,在模式串中不存在,所以模式串可以往后华滑动,因为只要有c存在,那么该字串肯定与模式串不匹配。
    在这里插入图片描述
  • BM算法,本质上其实就是在寻找这种规律,借助这种规律可以高效的实现字符串匹配。为了实现正确的滑动,BM算法包含了两个部分,分别是坏字符规则(bad character rule)和好后缀规则(good suffix shift)

坏字符规则

坏字符的定义

BM算法的匹配顺序是按照模式串的下标从大到小的顺序进行匹配的,当模式串的末尾倒着匹配中,发现某个字符没法匹配,我们就叫主串中的这个字符是坏字符
在这里插入图片描述

坏字符的移动

当模式串和主串的某个字串发生不匹配时,我们把坏字符串对应模式串的那个字符下标记作si,如果坏字符串在模式串中存在,我们把这个坏字符串在模式串中的下标记作xi,如果不在模式串中存在,则xi = -1。那么此时,模式串往后移动的位数就等于si - xi 。
在这里插入图片描述

好后缀规则

好后缀的定义

在模式串和主串字串匹配过程中,匹配上的字符串我们叫做好后缀
在这里插入图片描述

好后缀的移动

  • 我们把已经匹配的子串,在模式串中查找,如果找到另外一个跟好后缀匹配的模式串子串{u},此时,移动位数=好后缀在模式串中的当前位置 - 好后缀在模式串上一次出现的位置(模式串倒数第二个好后缀),如果好后缀只在模式串中出现一次,则上一次出现位置的值为-1.
    在这里插入图片描述
  • 如果在模式串中找不到另外一个等于好后缀的字串,那么我们就直接将模式串,滑动到主串好后缀的后面。
    在这里插入图片描述
  • 过度滑动的情况:当模式串滑动到前缀与主串中{u}的后缀有部分重合的时候,并且重合的部分相等的时候,就有可能会存在完全匹配的情况。针对这种情况,我们不仅要看好后缀在模式串中,是否有另一个匹配的子串,我们还要考察好后缀的后缀子串(c),是否存在跟模式串的前缀子串(c)匹配的。
    在这里插入图片描述

算法实现

package com.xxliao.algorithms.string_match.bm;

import java.util.Arrays;

/**
 * @author xxliao
 * @description: 字符串匹配 BM算法
 * @date 2024/5/31 16:19
 */

public class BMMatch {
    
    public static void main(String[] args) {
        String main = "acabcbcbacabc";
        String pattern = "cbacabc";
        System.out.println(indexOf(main,pattern));
    }

    private static final int CHARACTER_SIZE = 256; // 英文字符的种类,2^8

    /**
     * @description  BM算法匹配字符串,匹配成功返回P在S中的首字符下标,匹配失败返回-1
     * @author  xxliao
     * @date  2024/5/31 16:20
     */
    public static int indexOf(String main, String pattern) {

        char[] main_array = main.toCharArray();
        char[] pattern_array = pattern.toCharArray();
        int main_length = main_array.length;
        int pattern_length = pattern_array.length;

        // 模式串为空字符串,返回0
        if (pattern_length == 0) {
            return 0;
        }
        // 主串长度小于模式串长度,返回-1
        if (main_length < pattern_length) {
            return -1;
        }

        int[] bad_char_array = buildBadCharacter(pattern_array);
        int[] good_char_array = buildGoodSuffix(pattern_array);

        // 从尾部开始匹配,其中i指向主串,j指向模式串
        for (int i = pattern_length - 1; i < main_length; ) {
            int j = pattern_length - 1;
            for (; main_array[i] == pattern_array[j]; i--, j--) {
                if (j == 0) {   // 匹配成功返回首字符下标
                    return i;
                }
            }
            // 每次后移“坏字符规则”和“好后缀规则”两者的较大值
            // 注意此时i(坏字符)已经向前移动,所以并非真正意义上的规则
            i += Math.max(bad_char_array[main_array[i]], good_char_array[pattern_length - 1 - j]);
        }

        return -1;
    }

    /**
     * @description  坏字符规则表,数组内默认填充的是后移位数
     * @author  xxliao
     * @date  2024/5/31 16:30
     */
    private static int[] buildBadCharacter(char[] pattern) {
        int pattern_length = pattern.length;
        int[] bad_char_array = new int[CHARACTER_SIZE]; // 记录坏字符出现时后移位数

        Arrays.fill(bad_char_array, pattern_length);  // 默认后移整个模式串长度

        for (int i = 0; i < pattern_length - 1; i++) {
            int ascii = pattern[i];  // 当前字符对应的ASCII值
            bad_char_array[ascii] = pattern_length - 1 - i;   // 对应的后移位数,若重复则以最右边为准
        }

        return bad_char_array;
    }

    /**
     * @description  非真正意义上的好字符规则表,后移位数还加上了当前好后缀的最大长度
     * @author  xxliao
     * @date  2024/5/31 16:30
     */
    private static int[] buildGoodSuffix(char[] pattern) {
        int pattern_length = pattern.length;
        int[] good_char_array = new int[pattern_length];   // 记录好后缀出现时后移位数
        int last_prefix_pos = pattern_length;   // 好后缀的首字符位置

        for (int i = pattern_length - 1; i >= 0; i--) {
            // 判断当前位置(不含)之后是否是好后缀,空字符也是好后缀
            if (isPrefix(pattern, i + 1)) {
                last_prefix_pos = i + 1;
            }
            // 如果是好后缀,则GS=pLen,否则依次为pLen+1、pLen+2、...
            good_char_array[pattern_length - 1 - i] = last_prefix_pos - i + pattern_length - 1;
        }

        // 上面在比较好后缀时,是从模式串的首字符开始的,但实际上好后缀可能出现在模式串中间。
        // 比如模式串EXAMPXA,假设主串指针在比较P时发现是坏字符,那么XA就是好后缀,
        // 虽然它的首字符X与模式串的首字符E并不相等。此时suffixLen=2表示将主串指针后移至模式串末尾,
        // pLen-1-i=4表示真正的好字符规则,同样主串指针后移,使得模式串前面的XA对齐主串的XA
        for (int i = 0; i < pattern_length - 1; i++) {
            int suffixLen = suffixLength(pattern, i);
            good_char_array[suffixLen] = pattern_length - 1 - i + suffixLen;
        }

        return good_char_array;
    }

    /**
     * @description  判断是否是好后缀,即模式串begin(含)之后的子串是否匹配模式串的前缀
     * @author  xxliao
     * @date  2024/5/31 16:30
     */
    private static boolean isPrefix(char[] pattern, int begin) {
        for (int i = begin, j = 0; i < pattern.length; i++, j++) {
            if (pattern[i] != pattern[j]) {
                return false;
            }
        }

        return true;
    }

    /**
     * @description  返回模式串中以pattern[begin](含)结尾的后缀子串的最大长度
     * @author  xxliao
     * @date  2024/5/31 16:30
     */
    private static int suffixLength(char[] pattern, int begin) {
        int suffixLen = 0;

        int i = begin;
        int j = pattern.length - 1;
        while (i >= 0 && pattern[i] == pattern[j]) {
            suffixLen++;
            i--;
            j--;
        }
        return suffixLen;
    }
}

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Starry-Leo

帮到了您,有闲钱,再打赏哦~

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

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

打赏作者

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

抵扣说明:

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

余额充值