常见字符串匹配算法Python实现
class StringMatching(object):
"""常见字符串匹配算法"""
@staticmethod
def bf(main_str, sub_str):
"""
BF 是 Brute Force 的缩写,中文叫作暴力匹配算法
在主串中,检查起始位置分别是 0、1、2…n-m 且长度为 m 的 n-m+1 个子串,看有没有跟模式串匹配的
"""
a = len(main_str)
b = len(sub_str)
for i in range(a - b + 1):
if main_str[i:i + b] == sub_str:
return i
return -1
@staticmethod
def generate_bc(sub_str):
"""
:param sub_str: 是模式串
:return: 返回一个散列表 散列表的下标是 b_char 中每个字符的 ascii 码值,下标对应的值是该字符在b_char中最后一次出现的位置
"""
ascii_size = 256
ascii_list = [-1] * ascii_size
for i in range(0, len(sub_str)):
ascii_val = ord(sub_str[i])
ascii_list[ascii_val] = i
return ascii_list
@staticmethod
def move_by_gs(j, m, suffix, prefix):
"""
:param j: 表示坏字符对应的模式串中的字符下标
:param m: m 表示模式串的长度
:param suffix: suffix 数组中下标k,表示后缀子串的长度,下标对应的数组值存储的是后缀子串在字符串sub_str的位置
:param prefix: 而 prefix 数组中下标k,表示后缀子串的长度,对应的值 True 或者 False 则表示该后缀子串跟相同长度的前缀子串是否相对
:return: 应该往后滑动的距离
"""
k = m - 1 - j
if suffix[k] != -1:
return j - suffix[k] + 1
for r in range(j + 2, m):
if prefix[m - r]:
return r
return m
@staticmethod
def generate_gs(sub_str):
"""
假如字符串sub_str = cabcab, 那么后缀子串有[b,ab,cab,bcab,abcab]
suffix 数组中下标k,表示后缀子串的长度,下标对应的数组值存储的是后缀子串在字符串sub_str的位置
如:suffix[1] = 2, suffix[2] = 1, suffix[3] = 0, suffix[4] = -1, suffix[5] = -1
而 prefix 数组中下标k,表示后缀子串的长度,对应的值 True 或者 False 则表示该后缀子串跟相同长度的前缀子串是否相对
如 prefix[1] = False, prefix[2] = False, prefix[3] = True (表示 后缀cba = 前缀cba), prefix[4] = False, prefix[5] = False,
"""
m = len(sub_str)
suffix = [-1] * m
prefix = [False] * m
for i in range(0, m - 1):
j = i
k = 0
while j >= 0 and sub_str[j] == sub_str[m - 1 - k]:
j -= 1
k += 1
suffix[k] = j + 1
if j == -1:
prefix[k] = True
return suffix, prefix
def bm_simple(self, main_str, sub_str):
"""
:param main_str: 主字符串
:param sub_str: 模式串
:return:
仅用坏字符规则,并且不考虑 si-xi 计算得到的移动位数可能会出现负数的情况的代码实现如下
"""
bc = self.generate_bc(sub_str)
i = 0
n = len(main_str)
m = len(sub_str)
while i <= n - m:
j = m - 1
while j >= 0:
if main_str[i + j] != sub_str[j]:
break
j -= 1
if j < 0:
return i
bad_str_index = bc[ord(main_str[i + j])]
i += (j - bad_str_index)
return -1
def bm(self, main_str, sub_str):
"""
:param main_str: 主字符串
:param sub_str: 模式串
:return:
"""
bc = self.generate_bc(sub_str)
suffix, prefix = self.generate_gs(sub_str)
i = 0
n = len(main_str)
m = len(sub_str)
while i <= n - m:
j = m - 1
while j >= 0:
if main_str[i + j] != sub_str[j]:
break
j -= 1
if j < 0:
return i
x = j - bc[ord(main_str[i + j])]
y = 0
if j < m - 1:
y = self.move_by_gs(j, m, suffix, prefix)
i = i + max(x, y)
return -1
@staticmethod
def get_next_list(sub_str):
"""计算如: abababzabababa 每个位置的前缀集合与后缀集合的交集中最长元素的长度
输出为: [-1, -1, 0, 1, 2, 3, -1, 0, 1, 2, 3, 4, 5, 4]
计算最后一个字符 a 的逻辑如下:
abababzababab 前缀后缀最大匹配了 6 个(ababab),次大匹配是4 个(abab),次大匹配的前缀后缀只可能在 ababab 中,
所以次大匹配数就是 ababab 的最大匹配数,在数组中查到出该值为 3。第三大的匹配数同理,它既然比 3 要小,那前缀后缀也只能在 abab 中找,
即 abab 的最大匹配数,查表可得该值为 1。再往下就没有更短的匹配了。来计算最后位置 a 的值:既然末尾字母不是 z,
那么就不能直接 5+1=7 了,我们回退到次大匹配 abab,刚好 abab 之后的 a 与末尾的 a 匹配,所以 a 处的最大匹配数为 4。
"""
m = len(sub_str)
next_list = [None] * m
next_list[0] = -1
k = -1
i = 1
while i < m:
while k != -1 and sub_str[k + 1] != sub_str[i]:
k = next_list[k]
if sub_str[k + 1] == sub_str[i]:
k += 1
next_list[i] = k
i += 1
return next_list
def kmp(self, main_str, sub_str):
n = len(main_str)
m = len(sub_str)
next_list = self.get_next_list(sub_str)
i = 0
j = 0
while i < n and j < m:
if j == -1 or main_str[i] == sub_str[j]:
i += 1
j += 1
else:
j = next_list[j]
if j == m:
return i - j
return -1