最简单的暴力方法大家应该都会写,我们来看一下优化。
最主要的问题在于,能不呢利用之前已经匹配的一部分,进行继续匹配,而不是从头开始
BM算法
基于预处理来避免不必要的重复。从模式串的末尾,往前匹配
坏字符规则:发现主串坏字符,检查模式串中是否存在坏字符,不存在直接跳过
坏字符的作用是跳过一些肯定不可能成立的匹配位置,描述的是主串上的失配字符。
当我们对其s和p之后,从p的末尾即p[6]开始匹配。发现和s[6]不一致,此时s中不匹配的s[6]=S,就是坏字符
发现坏字符之后。检查模式串p中是否有S,没有,直接从S[7]开始匹配
因为没有就说明了,模式串中任何一个部分都不可能与目前的s重叠,跳过这一段,从主串的下一位置开始匹配
此时同样P E两个字符不匹配,检查模式串中是否存在坏字符P。有
此时将模式串最后一次出现的P,与s对齐
注意,这里最后一次出现的位置可能比坏字符位置大,导致实际上模式串往后移动了负数位,即往前移动了
所以模式串移动的位数应该和1取max,以保证至少移动一位
好后缀规则:
我们注意到,SIMPLE 和 EXAMPLE 的匹配中,我们发现“MPLE”都匹配上。
直到主串的I和模式串的A,我们称“MPLE”为一个好后缀。
根据坏字符规则,此时坏字符I在模式串中不存在,应该移动到I的后一位。即 A在模式串的下标2-(I在模式串中不存在为-1)=3位。
好后缀规则,就是想让其多移动几位。
我们拿出好后缀,看其字串“PLE”、“LE”、“E”是否出现在模式串EX中。
这里E出现了,于是我们把模式串的E和主串的E对齐。
代码实现
坏字符最右位置计算:
def get_bc(pattern):
bc = dict() # 记录每个badchar最右出现的位置
for i in range(len(pattern) - 1):
char = pattern[i]
bc[char] = i + 1
return bc
好后缀偏移表计算:
def get_gs(pattern):
gs = dict()
gs[''] = len(pattern)
# suf_len 用于标记后缀长度
for suf_len in range(len(pattern)):
suffix = pattern[len(pattern) - suf_len - 1:]
# j 用于标记可用于匹配的位置
for j in range(len(pattern) - suf_len - 1):
substr = pattern[j:j + suf_len + 1]
if suffix == substr:
gs[suffix] = len(pattern) - j - suf_len - 1
for suf_len in range(len(pattern)):
suffix = pattern[len(pattern) - suf_len - 1:]
if suffix in gs: continue
gs[suffix] = gs[suffix[1:]]
gs[''] = 0
return gs
匹配过程:
def bm(string, pattern, bc, gs):
# i 用于标记当前模式串和主串哪个位置左对齐。
i = 0
# j 用于标记当前模式串匹配到哪个位置;从右往左遍历匹配。
j = len(pattern)
while i < len(string) - len(pattern) and (j > 0):
# 从右往左匹配每个位置
a = string[i + j - 1]
b = pattern[j - 1]
if a == b: # 匹配的上,继续匹配前一位
j = j - 1
else: # 匹配不上,根据两个规则的预处理结果进行快速移动
i = i + max(gs.setdefault(pattern[j:], len(pattern)), j - bc.setdefault(a, 0))
j = len(pattern)
# 匹配成功返回匹配位置
if j == 0:
return i
# 匹配失败返回 None
return -1
if __name__ == '__main__':
string = 'here is a simple example '
pattern = 'example'
bc = get_bc(pattern) # 坏字符表
gs = get_gs(pattern) # 好后缀表
print(gs)
x = bm(string, pattern, bc, gs)
print(x)