zoukankan      html  css  js  c++  java
  • 字符串匹配算法之BM算法

      BM算法,全称是Boyer-Moore算法,1977年,德克萨斯大学的Robert S. Boyer教授和J Strother Moore教授发明了一种新的字符串匹配算法。

    BM算法定义了两个规则:

    1、坏字符规则:当文本串中的某个字符跟模式串的某个字符不匹配时,我们称文本串中的这个失配字符为坏字符,此时模式串需要向右移动,移动的位数 = 坏字符在模式串中的位置 - 坏字符在模式串中最右出现的位置。此外,如果"坏字符"不包含在模式串之中,则最右出现位置为-1。
    2、好后缀规则:当字符失配时,后移位数 = 好后缀在模式串中的位置 - 好后缀在模式串上一次出现的位置,且如果好后缀在模式串中没有再次出现,则为-1。

    关于坏字符规则和好后缀规则的具体讲解,以及怎么移动,可以查看阮一峰老师的详细讲解:http://www.ruanyifeng.com/blog/2013/05/boyer-moore_string_search_algorithm.html

    这里根据讲解画了两张图,方便自己理解坏字符规则:

     

     2019年11月14日15:38:41 修改

    具体代码如下:

      1     private static final int SIZE = 256; // 全局变量或者是局部变量
      2 
      3     /**
      4      * 坏字符规则哈希表构建方法
      5      * 
      6      * @param b
      7      *            模式串
      8      * @param m
      9      *            模式串的长度
     10      * @param bc
     11      *            散列表
     12      */
     13     private void generateBC(char[] b, int m, int[] bc) {
     14         for (int i = 0; i < SIZE; ++i) {
     15             bc[i] = -1; // 初始化bc
     16         }
     17 
     18         for (int i = 0; i < m; ++i) {
     19             int ascii = (int) b[i]; // 计算b[i]的ASCII值
     20             bc[ascii] = i;
     21         }
     22     }
     23 
     24     /**
     25      * 好后缀规则构建哈希表
     26      * 
     27      * @param b
     28      *            模式串
     29      * @param m
     30      *            模式串长度
     31      * @param suffix
     32      *            suffix数组的下标 k,表示后缀子串的长度,
     33      *            下标对应的数组值存储的是,在模式串中跟好后缀{u}相匹配的子串{u*}的起始下标值
     34      * @param prefix
     35      *            记录模式串的后缀子串是否能匹配模式串的前缀子串
     36      */
     37     private void generateGS(char[] b, int m, int[] suffix, boolean[] prefix) {
     38         for (int i = 0; i < m; ++i) { // 初始化
     39             suffix[i] = -1;
     40             prefix[i] = false;
     41         }
     42 
     43         for (int i = 0; i < m - 1; ++i) {
     44             int j = i;
     45             int k = 0; // 公共后缀子串长度
     46             while (j >= 0 && b[j] == b[m - 1 - k]) { // 与b[0, m-1]求公共后缀子串
     47                 --j;
     48                 ++k;
     49                 suffix[k] = j + 1; // j+1表示公共后缀在b[0,i]中的起始下标
     50             }
     51             if (j == -1) {
     52                 prefix[k] = true; // 如果公共后缀子串也是模式串的后缀子串
     53             }
     54         }
     55     }
     56 
     57     /**
     58      * 完整的BM算法 好后缀+坏字符
     59      * 
     60      * @param a
     61      *            主串
     62      * @param n
     63      *            主串的长度
     64      * @param b
     65      *            模式串
     66      * @param m
     67      *            模式串的长度
     68      * @return
     69      */
     70     public int bm(char[] a, int n, char[] b, int m) {
     71         int[] bc = new int[SIZE];
     72         generateBC(b, m, bc); // 构建坏字符哈希表
     73         int[] suffix = new int[m];
     74         boolean[] prefix = new boolean[m];
     75         generateGS(b, m, suffix, prefix); // 构建好字符哈希表
     76         int i = 0; // j 表示主串与模式串匹配的第一个字符
     77         while (i < n - m) {
     78             int j = 0;
     79             for (j = m - 1; j >= 0; --j) {// 模式串从后向前匹配
     80                 if (a[i + j] != b[j]) {
     81                     break; // 坏字符串
     82                 }
     83             }
     84             if (j < 0) {
     85                 return i;// 匹配成功,返回主串和模式串第一个匹配字符的位置
     86             }
     87             int x = j - bc[(int) a[i + j]];
     88             int y = 0;
     89             if (j < m - 1) { // 如果有好后缀的话
     90                 y = moveByGS(j, m, suffix, prefix);
     91             }
     92             i = i + Math.max(x, y);
     93         }
     94         return -1;
     95     }
     96 
     97     private int moveByGS(int j, int m, int[] suffix, boolean[] prefix) {
     98         int k = m - 1 - j; // 好后缀的长度
     99         if (suffix[k] != -1) {
    100             return j - suffix[k] + 1;
    101         }
    102         for (int r = j + 2; r <= m - 1; ++r) {
    103             if (prefix[m - r] == true) {
    104                 return r;
    105             }
    106         }
    107         return m;
    108     }    

     现在再看BM算法,原来之前自己是一点也没弄懂!只是当做文章简单读了一遍,阿西吧!

    首先,那个坏字符的散列表的构建就没有弄懂:

    1、为什么在散列表数组中要初始化每一个值为-1?

    这里是在坏字符匹配的时候,如果主串与模式串中字符没有匹配上(把坏字符在模式串中下标记做xi),此时的xi=-1

    2、你有没有考虑过模式串中的相同的字符的ASCII码是相同的,那样循环处理的话,只是记录模式串中相同字符中最后面的那个字符的下标,没有问题吗?

    这个是没有问题的!这里无非有两种情况,就是坏字符与非坏字符:

    坏字符:因为是从后向前倒序匹配,只需要知道后面的字符下标,就可以计算出移动距离

    非坏字符:需要去寻找坏字符或者使用好后缀规则

    2019年11月14日15:34:37 修改

    此大部分内容来自极客时间专栏,王争老师的《数据结构与算法之美》

    极客时间:https://time.geekbang.org/column/intro/126

  • 相关阅读:
    Android 屏幕适配比例
    不错的网站
    Android十大常用技术揭秘-挑战
    Linux 自己的常用命令
    Android 常用配置
    Android 边框 给控件添加边框
    Linux 常用命令大全
    TCP/IP、Http、Socket的区别
    Android 之基于 HTTP 协议的通信详解
    JavaScript基础:(加号,数值转换,布尔转换)
  • 原文地址:https://www.cnblogs.com/ssh-html/p/10118496.html
Copyright © 2011-2022 走看看