1.朴素 算法
"""
朴素算法:
一种字符串匹配算法,
在一个较长的原字符串中寻找某个子串并返回其首次出现的索引
思路:
使用子串中的第0个字符去匹配原串中的每一个字符,
直到找到和子串第0个字符相同的字符,
再一一匹配后面的字符,若后面所有字符都相同,则匹配成功;
若有一个不相同则子串从第0个字符继续在原串向后匹配,
直到找到匹配的子串或搜索结结束
"""
def isMatch(src, dst):
"""
朴素算法
:param src: 原字符串
:param dst: 目标子串
:return: 子串首次出现的索引
"""
# 分别获取长度
src_len, dst_len = len(src), len(dst)
# 遍历下标到 m-n+1,后面不用遍历了,因为长度不够了
for i in range(src_len - dst_len + 1):
# 若相同
if src[i:i + dst_len] == dst:
# 返回下标
return i
# 不匹配 返回-1
return -1
if __name__ == '__main__':
src = 'byte bye-by bye-bye'
dst = 'bye-bye'
print(isMatch(src, dst))
2. KMP 算法
"""
KMP 算法
优化了朴素算法的比较次数,大大提高了匹配效率
思路:
KMP算法优化的比较次数需要通过被搜索的子串来计算,
计算的依据是子串的前缀和后缀的最长共有长度, 也被称为部分匹配表
"""
部分分配表-详
部分分配表-简
"""
举例:
要在 byte bye-by bye-bye 中 搜索bye-bye,需要通过子串bye-bye 生成部分匹配表
然后使用子串bye-bye去原字符串匹配字符,
第一次匹配到部分字符相同时,字符b和字符y都匹配,但是t与e不匹配,
如果用朴素算法,下一次使用子串中的b去匹配元字符串中的y,即向后移动一位
KMP算法移动位数通过公示计算: 移动位数 = 已比较长度 - 对应的部分匹配表值
此时已比较长度为2,对应的部分匹配表的值为0,所以此时向后移动位数 = 2 - 0 = 2
"""
def getSteps(dst):
# index 初始值为0 用于记录最长匹配长度
index, dst_len = 0, len(dst)
# 用于存储部分匹配表, 默认值为0
steps = [0] * dst_len
# 从第一个字符开始向后遍历
for i in range(1, dst_len):
# 若当前遍历字符和index的字符相同
if dst[i] == dst[index]:
# index + 1 为当前的长度
steps[i] = index + 1
# 然后 index + 1
index += 1
# 若当前遍历字符和index的字符不相同,且index不为0
elif index != 0:
index = steps[index - 1]
# 字符不匹配,且index为0则当前匹配长度为0
else:
steps[i] = 0
return steps
def kmp(src, dst):
"""
KMP 算法
:param src: src原字符串
:param dst: dst子串
:return: 子串在原串中首次出现的索引下标
"""
steps = getSteps(dst)
# 获取两个串的长度
src_len, dst_len = len(src), len(dst)
# 初始下标
i, j = 0, 0
while i < src_len and j < dst_len:
# 若相同, 继续向后匹配
if src[i] == dst[j]:
i += 1
j += 1
# 根据部分匹配表决定j向后移动的位数
elif j != 0:
j = steps[j - 1]
else:
i += 1
# 若长度相同,返回下标
if j == dst_len:
return i - j
else:
return -1
if __name__ == '__main__':
src = 'byte bye-by bye-bye'
dst = 'bye-bye'
print(kmp(src, dst))
3. Boyer-Moore 算法
"""
Boyer_Moore算法 也称为BM算法
目前使用最广泛的字符串匹配算法, 大多数文本编辑器的查找功能使用了此算法
举例:
here is a simple example 中 匹配 example
使用子串比较时,每次都是从后向前比较,找到第一个不相同的字符,
然后使用坏字符规则计算一个向后移动的位数
坏字符规则公式 移动位数 = 坏字符的位置 - 子串中上一次出现坏字符的位置(未出现过时-1)
当不匹配的字符并不是最后一个时,还需要根据 好后缀规则 计算一个移动的位数,
取两种规则中最大数 作为移动位数
好字后缀规则公式 移动位数 = 好后缀的位置 - 后缀在子串中上一次出现位置
"""
def index(dst, start, c):
"""
在src中从start处向前找上一次字符出现的位置
:param dst: 子串
:param start: 字符不相同的位置
:param c: 原串中不相同的字符
:return:
"""
start -= 1
while start >= 0:
if dst[start] == c:
return start
start -= 1
return -1
def compare(src, dst):
"""
比较两个字符串
:param src: 原串中部分子串
:param dst: 对比子串
:return:
"""
# 记录下标, 从后向前比
src_len = len(src) - 1
dst_len = len(dst) - 1
# 若最后的下标不同, 说明长度不同 返回 -2
if src_len != dst_len:
return -2, None
# 循环比较, 从后向前比较
while src_len >= 0:
# 若某个字符串不同, 返回该字符的下标级原字符串的不相同的字符
if src[src_len] != dst[src_len]:
return src_len, src[src_len]
src_len -= 1
# 完全相同 返回-3
return -3, None
def searchSuffix(dst, suffix):
"""
获取好后缀位置
:param dst:
:param suffix:
:return:
"""
i, l = 0, len(suffix)
# 寻找最长后缀
while i + 1 < len(dst) - 1:
# 找到最长后缀
if dst[i:i + 1] == suffix:
return i
i += 1
# 若while循环未找到最长后缀
for i in range(l):
# 判断是否以其他后缀开始
if src.startswith(suffix[i:]):
return 0
# 未找到任何后缀
return -1
def bm(src, dst):
"""
bm算法
:param src: src 原字符串
:param dst: 子串
:return:
"""
# 目标串长度
l = len(dst) - 1
# 原字符串长度
start, end = 0, 0
# 每次循环从原字符串中取出子串长度得字符串进行比较
while True:
# 计算取子串时得结束位置
end = start + len(dst)
print(f'查看原串中 子串长度的一段字符串 进行比较 {src[start:end]}')
# 从原串中取出子串并比较
cr = compare(src[start:end], dst)
# 最后的下标不同, 长度不同
if cr[0] == -2:
break
# 比较的两个字符串相同,直接返回下标
if cr[0] == -3:
return end - 1
else:
# 获取不相同字符上一次出现的位置
pos = index(dst, cr[0], cr[1])
# 最后一个字符不相同
if cr[0] == len(dst) - 1:
# 不相同的字符在子串中
if pos != -1:
# 向后移动位数 = 坏字符串位置 - 不相同字符在子串中的位置
start += cr[0] - pos
# 不相同的字符不在子串中
else:
# 开始下标直接向后移动子串长度位
start += len(dst)
# 中间某个字符不同
else:
# 坏字符规则
step_bad = cr[0] - pos
# 获取后缀
suffix = dst[cr[0] + 1:]
# 好后缀规则 获取后缀上次出现的位置
suffix_index = searchSuffix(dst, suffix)
# 好后缀规则 计算移动的位数
step_good = l - suffix_index
start += step_bad if step_bad > step_good else step_good
return -1
if __name__ == '__main__':
src = 'here is a simple example'
dst = 'example'
print(bm(src, dst))
控制台输出
查看原串中 子串长度的一段字符串 进行比较 here is
查看原串中 子串长度的一段字符串 进行比较 a simp
查看原串中 子串长度的一段字符串 进行比较 simple
查看原串中 子串长度的一段字符串 进行比较 exampl
查看原串中 子串长度的一段字符串 进行比较 example
23