BM算法

来源:互联网 发布:js保存数据到本地文件 编辑:程序博客网 时间:2024/06/06 01:47

BM 算法

之前写过关于KMP算法的实现,之后又发现了目前运用于实际的还是BM算法。貌似BM算法比kmp算法快3~5倍的样子。

举例:

字符串 HERE IS A SIMPLE EXAMPLE 搜索词 EXAMPLE

说明:

  1. 我们以前搜索的方式都是从前往后,即头部对齐。但是这样有的时候效率会比较低,BM算法采取了一种聪明的方式,从后往前进行比较。
    • 在这里就是”HERE IS”与”EXAMPLE”比较,取最后一位“S”与“E”。不匹配,所以我们继续向后移动。我们还发现,“S”并未出现在”EXAMPLE”之中,所以在这里我们可以直接向后移动7位(搜索词的长度)。
  2. 同时,这里我们定义一个词坏字符。比如这里的“S”,与“E”(搜索词最后一位)不匹配,所以称“S”为坏字符。
  3. 下一步,我们比较” A SIMP”。我们同样发现,”P”与”E”不匹配,但是”P”在搜索词(”EXAMPLE”)中出现了。所以不能移动7位,那我们就只能一位一位的移动了吗?答案是否定的,这里出现了一个新的规则——”坏字符规则”。
  4. 后移位数=坏字符的位置-搜索词中的上一次出现位置。如果”坏字符”不包含在搜索词中,那么”搜索词中的上一次出现位置”定义为-1。这里可以从上一步的”S”得到验证。后移位数=6(1)=7 (从零开始编号)。坏字符的位置是对比于在搜索词的位置。比如这里的”P”,我们需要后移的位数=64=2
  5. 所以我们后移了两步,这里比较的是” SIMPLE”与”EXAMPLE”。我们发现”MPLE”与”MPLE”匹配成功,但是再往前就匹配失败了。所以我们这里应该后移的位数=2(1)=3,这里的坏字符是”A”。那么这是最好的移动方式吗?答案同样是否定的。
  6. 在这里,我们定义一个词好后缀 。所有尾部匹配的字符串,比如这里的”MPLE”、”PLE”、”LE”、”E”都是好后缀
  7. 好后缀规则后移位数=好后缀的位置-搜索词上一次出现位置。这里好后缀的位置是6(最后一个字符的位置,这里指的是”E”)。同坏字符规则,我们搜索词上一次出现位置应该是-1,但是这里不一样的地方在于,我们好后缀不止一个,所以我们需要逐一比较。比如这里”MPLE”这个好后缀对应的是-1,”PLE”对应的是-1,”LE”对应的是-1,”E”对应的是0。最后我们取0这个值作为搜索词上一次出现位置。所以后移位数=60=6
  8. 后面移动的方式同上操作,就不赘述了。到最后我们可以发现,其实每次的移动位数就是,取坏字符规则好后缀规则移动位数大的位数移动。而且,坏字符规则表与好后缀规则表可以事先生成,而不需要原字符串。

步骤

  1. 移动位数:7,匹配字符串:”HERE IS”
  2. 移动位数:2,匹配字符串:” A SIMP”
  3. 移动位数:6,匹配字符串:”E EXAMP”
  4. 移动位数:2,匹配字符串:”EXAMPLE”

总结

  1. 搜索规则:从后往前搜索
  2. 编号从0开始
  3. 坏字符:不匹配的字符
  4. 坏字符位置:坏字符对比于在搜索词中的位置
  5. 搜索词的上一次出现位置:坏字符/好后缀在搜索词中上一次出现的位置(从后往前)
  6. 好后缀:所有尾部匹配的字符串
  7. 好后缀后移位数需要多次取值。

代码实现(python3)

# -*- coding: utf-8 -*-# 查找搜索词上一次出现的位置def string_index(string, string2):    try:        str_index = string.index(string[string.index(string2)]) + len(string2)        index2 = string[str_index:].index(string2) + len(string2)    except:        index2 = -1    return index2# 生成好后缀集合def generate_good_suffix_set(string_match):    for num in range(len(string_match)):        string_flag = string_match[: num + 1]        suffix_list = [string_flag[num:]                       for num in range(len(string_flag))]    good_rule = {}    for item in suffix_list:        good_rule[item] = string_index(string_match, item)    return good_rule# 生成坏字符集合def gengerate_bad_char_set(string_match):    bad_rule = {}    bad_char_lst = [item for item in string_match]    for char in bad_char_lst:        bad_rule[char] = string_match.index(char)    return bad_ruledef find_string(string_origin, string_match):    flag = False    index_origin = len(string_match)    # 好后缀集合    good_rule = generate_good_suffix_set(string_match)    bad_rule = gengerate_bad_char_set(string_match)    # 从搜索词长度开始搜索,反向匹配    while index_origin <= len(string_origin):        index_flag = index_origin        # 原字符串的提取        string_origin_temp = string_origin[index_origin - 7:index_origin]        # 统计匹配成功的字符串长度        count = 0        # 反向遍历搜索词        for index_match in range(len(string_match)):            if (string_origin[index_flag - 1] != string_match[::-1][index_match]):                # 坏字符+好后缀字符串                string_temp = string_origin[index_flag - 1:index_flag + count]                # 坏字符                char = string_temp[0]                # 好后缀字符串                if len(string_temp) > 1:                    string_temp = string_temp[1:]                step1 = len(string_match) - index_match                # 坏字符规则                if char in string_match:                    step1 = step1 - bad_rule[char] - 1                # 好后缀规则                step2 = 1                if len(string_temp) > 1:                    for j in range(len(string_temp)):                        step2 = good_rule[string_temp[j:]]                        if step2 != -1:                            break                # 选出step1与step2更大的值                step = max(step1, step2)                break            else:                # 匹配成功,自加1                count += 1                # 继续匹配                index_flag -= 1                # 如果匹配至最后一个字符,则表示完全匹配成功                if index_match == len(string_match) - 1:                    flag = True                    break        if flag == True:            print(index_origin - len(string_match))            break        index_origin += step    return flag, index_origin - len(string_match)if __name__ == "__main__":    # 测试字符串    string_origin = "HERE IS A SIMPLE EXAMPLE"    string_match = "EXAMPLE"    string_origin = "BBC ABCDAB ABCDABCDABDE"    string_match = "ABCDABD"    flag, index = find_string(string_origin, string_match)    print(flag, string_origin[index:])
原创粉丝点击