四种最常见的字符串匹配算法概述
1 BF算法:
BF算法,即暴力(Brute Force)算法,是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。BF算法是一种蛮力算法。
def naive_matching(t, p): m, n = len(p), len(t) i, j = 0, 0 while i < m and j < n: # i==m means a matching if p[i] == t[j]: # ok! consider next char in p i, j = i + 1, j + 1 else: # no! consider next position in t i, j = 0, j - i + 1 if i == m: # find a matching, return its index return j - i return -1 # no matching, return special value t = "aabababababbbbaababaaaababababbab" p = "abbab" print(naive_matching(t, p))
2 KMP算法:
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)。KMP算法是各大教科书上的看家绝学,曾被投票选为当今世界最伟大的十大算法之一;但是晦涩难懂,并且十分难以实现。
def KMP_matching(t, p): """ KMP字符串匹配的另一个版本, 稍许修改(非本质). 将gen_pnext定义为局部函数. """ def gen_pnext(p): # p = "abbab" """生成p中各i的下一检查位置表,稍许优化版本.""" i, k, m = 0, -1, len(p) pnext = [-1] * m # 初始化一个数组 while i < m - 1: # generate pnext[i+1] if k == -1 or p[i] == p[k]: i, k = i + 1, k + 1 if p[i] == p[k]: pnext[i] = pnext[k] else: pnext[i] = k else: k = pnext[k] return pnext j, i = 0, 0 n, m = len(t), len(p) pnext = gen_pnext(p) while j < n and i < m: # i==m means a matching while i >= 0 and t[j] != p[i]: i = pnext[i] j, i = j + 1, i + 1 if i == m: # 找到匹配, 返回其下标 return j - i return -1 # 不存在匹配, 返回特殊值 t = "aabababababbbbaababaaaababababbab" p = "abbab" print(KMP_matching(t, p))
3 BM算法:
在计算机科学里,Boyer-Moore字符串搜索算法是一种非常高效的字符串搜索算法。它由Bob Boyer和J Strother Moore设计于1977年。此算法仅对搜索目标字符串(关键字)进行预处理,而非被搜索的字符串。虽然Boyer-Moore算法的执行时间同样线性依赖于被搜索字符串的大小,但是通常仅为其它算法的一小部分:它不需要对被搜索的字符串中的字符进行逐一比较,而会跳过其中某些部分。通常搜索关键字越长,算法速度越快。它的效率来自于这样的事实:对于每一次失败的匹配尝试,算法都能够使用这些信息来排除尽可能多的无法匹配的位置。
def getBMBC(pattern): # 预生成坏字符表 BMBC = dict() for i in range(len(pattern) - 1): char = pattern[i] # 记录坏字符最右位置(不包括模式串最右侧字符) BMBC[char] = i + 1 return BMBC def getBMGS(pattern): # 预生成好后缀表 BMGS = dict() # 无后缀仅根据坏字移位符规则 BMGS[''] = 0 for i in range(len(pattern)): # 好后缀 GS = pattern[len(pattern) - i - 1:] for j in range(len(pattern) - i - 1): # 匹配部分 NGS = pattern[j:j + i + 1] # 记录模式串中好后缀最靠右位置(除结尾处) if GS == NGS: BMGS[GS] = len(pattern) - j - i - 1 return BMGS def BM(string, pattern): """ Boyer-Moore算法实现字符串查找 """ m = len(pattern) n = len(string) i = 0 j = m indies = [] BMBC = getBMBC(pattern=pattern) # 坏字符表 BMGS = getBMGS(pattern=pattern) # 好后缀表 while i < n: while (j > 0): if i + j - 1 >= n: # 当无法继续向下搜索就返回值 return indies # 主串判断匹配部分 a = string[i + j - 1:i + m] # 模式串判断匹配部分 b = pattern[j - 1:] # 当前位匹配成功则继续匹配 if a == b: j = j - 1 # 当前位匹配失败根据规则移位 else: i = i + max(BMGS.setdefault(b[1:], m), j - BMBC.setdefault(string[i + j - 1], 0)) j = m # 匹配成功返回匹配位置 if j == 0: indies.append(i) i += 1 j = len(pattern) t = "aabababababbbbaababaaaababababbab" p = "abbab" print(BM(t, p))
4 Sunday算法:
Sunday算法是Daniel M.Sunday于1990年提出的字符串模式匹配。其核心思想是:在匹配过程中,模式串发现不匹配时,算法能跳过尽可能多的字符以进行下一步的匹配,从而提高了匹配效率。目前发现的最高效且容易理解的算法。
def sunday_match_str(main_str, find_str): """ :param main_str: 主串 :param find_str: 模式串 :return: 返回第一个匹配到模式串中第一个字符在主串中的下标 """ m, f = 0, 0 m_len, f_len = len(main_str), len(find_str) while m < m_len: if main_str[m] == find_str[f]: m, f = m + 1, f + 1 if f == f_len: return m - f_len # 此时找到了第一个匹配到的下标 continue else: flag = m - f + f_len if flag > m_len - 1: # main_str下标越界,没有找到匹配的串 return -1 check_exits = find_str.rfind(main_str[flag]) if check_exits != -1: # 在find_str中有匹配 jump = f_len - check_exits # 移动的步长 m, f = m - f + jump, 0 else: # 在find_str中无匹配 jump = f_len + 1 # 移动的步长 m, f = m - f + jump, 0 else: return -1 t = "aabababababbbbaababaaaababababbab" p = "abbab" print(sunday_match_str(t, p))