今天学字符串算法,先写基于字符对比的三种算法:朴素算法、KMP算法和BM算法。其他以后再补。
def index_match(src: str, dst: str, reverse=False) -> int:
"""
朴素算法,支持正向和反向查找(通过`reverse`参数控制)。
### 改进建议:
1. **内存效率:** 在反向查找情况下,创建反转的列表(或字符串,如采用改进后的反转方法)会增加额外的内存使用。如果对内存使用有较高要求,可以考虑不通过实际反转字符串来实现反向查找,而是通过调整索引方式来遍历字符串。
:param src: 源字符串
:param dst:目标子串
:param reverse:指示是否以反向模式查找。默认为False,表示正向查找;设为True时表示反向查找。
:return:目标子串在源字符串中的起始索引,找不到时为-1
"""
# 如果为反向查找,将源字符串和目标子串都反转
if reverse:
src = ''.join(reversed(src))
dst = ''.join(reversed(dst))
matched_num = 0 # 已匹配字符数
for i, s in enumerate(src):
if s == dst[matched_num]:
matched_num += 1
if matched_num == len(dst): return len(src) - 1 - (i - len(dst) + 1) if reverse else i - len(dst) + 1 # 如果全部字符匹配成功,返回起始索引
else:matched_num = 0 # 不匹配,重置已匹配字符数
return -1
def KMP_match(src: str, dst: str) -> int:
"""
KMP算法
### 改进建议:
1. **KMP字典构建优化**:当前的KMP字典构建算法在寻找最长前缀和后缀匹配时效率较低,因为它尝试所有可能的前后缀长度并进行字符串比较。更高效的实现无需对每个前后缀组合进行比较,而是可以通过前面已计算的值进行优化计算。
:param src: 源字符串
:param dst: 目标子串
:return: 目标子串在源字符串中的起始索引,找不到时为-1
"""
# 预处理部分,构建部分匹配表(KMP字典)
KMP_dict = {} # 存储每个前缀的最长匹配前后缀长度
for i in range(1, len(dst) + 1):
sub_dst = dst[:i]
# 获取sub_dst的最长匹配前后缀长度
for j in range(1, i):
if sub_dst[:j] == sub_dst[-j:]: KMP_dict[i] = j
index = matched_num = 0 # index为源字符串的索引,matched_num为匹配到的字符数
while index < len(src):
if src[index] == dst[matched_num]:
matched_num += 1
if matched_num == len(dst): return index - len(dst) + 1 # 如果所有字符匹配成功,返回起始索引
index += 1
else:
# 根据KMP字典调整索引和已匹配数量
index += 1 - KMP_dict.setdefault(matched_num, 0)
matched_num = KMP_dict[matched_num]
return -1
def BM_match(src: str, dst: str) -> int:
"""
BM算法
### 改进意见:
1. **优化搜索逻辑:** 在当前的实现中,每遇到一个不匹配的字符,就需要重新计算跳跃步数,并移动索引。这可以进一步优化,比如使用预先计算好的坏字符表和好后缀表来快速决定索引的移动步长,从而减少不必要的计算。
:param src: 源字符串
:param dst: 目标子串
:return: 目标子串在源字符串中的起始索引,找不到时为-1
"""
dst_max_index = len(dst) - 1 # 目标子串的最大索引
index = bad_str_index = dst_max_index # 源字符串和目标字符串当前比较的索引
# 循环遍历源字符串,直到搜索完成或无法找到匹配
while index < len(src) and bad_str_index >= 0:
# 如果当前字符匹配,则同时向前移动源和目标字符串的索引
if src[index] == dst[bad_str_index]:
index -= 1
bad_str_index -= 1
else:
# 若当前位置不匹配,根据坏字符规则和好后缀规则计算跳跃步数
if bad_str_index == dst_max_index:
index += bad_str_rule(bad_str_index, src[index], dst) # 如果不匹配的字符是目标字符串的最后一个字符,仅使用坏字符规则
else:
# 如果不匹配的字符位于目标字符串中间,结合坏字符规则和好后缀规则,取二者中较大的跳跃步数
index += max(bad_str_rule(bad_str_index, src[index], dst),
good_suffix_rule(bad_str_index + 1, dst)) + dst_max_index - bad_str_index
bad_str_index = dst_max_index # 重置目标字符串的当前检查位置为最大索引
return index + 1 if index < len(src) else -1
def bad_str_rule(bad_str_index: int, bad_str: str, dst: str) -> int:
"""
坏字符规则:
当在源字符串与目标子字符串比较过程中遇到不匹配的字符时,此规则找到目标子字符串中最靠右但不等于不匹配字符位置的相同字符,并据此计算搜索索引应跳跃的步数。
:param bad_str_index:当前在目标子字符串中不匹配的字符的索引
:param bad_str:当前在源字符串中不匹配的字符
:param dst:目标子字符串
:return:根据坏字符规则计算得到的跳跃步数
"""
last_bad_str_index = index_match(dst, bad_str, reverse=True) # 在目标子字符串中从右向左搜索与“坏字符”相同的字符的最后一个位置
move_num = bad_str_index - last_bad_str_index # 计算根据坏字符规则得到的跳跃步数
return move_num
def good_suffix_rule(good_suffix_index: int, dst: str) -> int:
"""
好后缀规则:
当在源字符串与目标子字符串比较时找到一个好后缀(即已匹配的子串部分)但紧随其前的字符不匹配时,此规则根据好后缀在目标子字符串中的其他位置或其部分子串的位置来计算索引应跳跃的步数。
:param good_suffix_index:好后缀在目标子字符串中的起始索引
:param dst:目标子字符串
:return:根据好后缀规则计算得到的跳跃步数
"""
longest_good_suffix = dst[good_suffix_index:] # 获取最长好后缀
last_good_suffix_index = index_match(dst[:good_suffix_index], longest_good_suffix, reverse=True) # 在目标子字符串中查找另一个等于最长好后缀的子串的最后位置
if last_good_suffix_index == -1:
# 如果目标子字符串中没有另一个等于最长好后缀的子串,则遍历所有非最长好后缀,判断起始位置是否有另一个等于好后缀的子串
for i in reversed(range(1, len(longest_good_suffix))):
good_suffix = longest_good_suffix[-i:]
if dst[:i] == good_suffix:
move_num = len(dst) - i
return move_num
else:return len(dst) # 如果没有找到匹配的好后缀子串,则表示需要将整个目标子字符串移过去
else:return len(dst) - 1 - last_good_suffix_index # 计算跳跃步数,使两个好后缀对齐
if __name__ == '__main__':
src, dst = 'ps:str is short for string', 'string'
print(f'index_match:match "{dst}" in "{src}"\nResult:{index_match(src, dst)}')
src, dst = 'byte bye-by bye-bye', 'bye-bye'
print(f'KMP_match:match "{dst}" in "{src}"\nResult:{KMP_match(src, dst)}')
src, dst = 'here is a simple example', 'example'
print(f'BM_match:match "{dst}" in "{src}"\nResult:{BM_match(src, dst)}')
输出:
index_match:match "string" in "ps:str is short for string"
Result:20
KMP_match:match "bye-bye" in "byte bye-by bye-bye"
Result:12
BM_match:match "example" in "here is a simple example"
Result:17