from collections import deque
def compute_nexts(pattern):
length = len(pattern)
high = length - 1 # 不计算包含最后一个字符的字符串的相同最长前后缀的长度
nexts = deque([0] * high)
i = 1
j = 0
while i < high:
if pattern[i] == pattern[j]:
nexts[i] = j + 1
i += 1
j += 1
else:
if j == 0:
nexts[i] = 0
i += 1
continue
j = nexts[j] # nexts 中的元素有两层含义 1 包含该字符的字符串的同最长前后缀的长度 2 包含该字符的字符串的同最长前后缀的最长前缀的下一个索引。此处用的是2的含义
# 1 将最长前后缀长度数组转成 数组索引下标表示已匹配字符串的下一个位置索引,数组元素表示最长可匹配前缀的下一个位置
nexts.appendleft(0)
return nexts
def kmp(main_string, pattern):
"""
kmp字符串匹配
算法思想:
在已匹配的字符串当中寻找到最长可匹配后缀子串和最长可匹配前缀子串,在下一轮直
接把两者对齐,从而实现模式串的快速移动。
具体的解析:
可参考https://baijiahao.baidu.com/s?id=1659735837100760934&wfr=spider&for=pc,
但获取next数组部分解析最好不要参考其,不易理解.对next数组的理解可参考https://blog.csdn.net/gmynebula/article/details/125239503 前缀表和next数组的关系:
next数组由最长公共前后缀长度数组获得。next数组=在最长公共前后缀长度数组最左侧插入一个0元素。
时间复杂度:
O(len(target)+len(pattern))
空间复杂度:
O(len(pattern)),空间next数组的大小是len(pattern)
:param target: 主串
:param pattern: 模式串
:return: 模式串在主串中的位置
"""
res = list()
nexts = compute_nexts(pattern)
i = 0 # i表示main_string下标
j = 0 # j表示pattern下标
while i < len(main_string):
while j < len(pattern):
if main_string[i] == pattern[j]:
i += 1
j += 1
else:
if j == 0:
i += 1
break
j = nexts[j]
if j == len(pattern):
pos = i - len(pattern)
res.append(pos) # 记录找到的位置
i = pos + 1 # 从已找到的位置的下一个位置再次开始寻找,查找是否还有其它的可以匹配的子串
j = 0 # 模式串从0开始
return res
target = 'abc;GTGTG566464kjlhlGTGTGSFDWD'
pattern = 'GTGTG'
res = kmp(target, pattern)
print(res)
target = "baecaecad"
pattern = "aeca"
res = kmp(target, pattern)
print(res)
"""
运行结果:
[4, 20]
[1, 4]
"""
[以下这块代码可能有问题,建议看上边代码]
# kmp_str_match.py
from array import array
from collections import deque
def next_pattern(pattern):
"""
计算模式串的next_数组
next_数组下标:
前缀的下一个位置
next_数组元素:
前缀的最长可匹配前缀的下一个位置
主串:
将模式串作为主串
模式串:
模式串本身
算法思想:
利用已计算出的next_数组中的元素计算剩余的next_数组中未计算出的元素,整体思想依然是KMP
思想,在已匹配的字符串当中寻找到最长可匹配后缀子串和最长可匹配前缀子串,在下一轮直接把两者
对齐,从而实现模式串的快速移动。
该算法代码结构与KMP代码结构类似,只是具体分支操作不同
:param pattern: 模式串
:return: next_数组
"""
pat_len = len(pattern) # 模式串长度
next_ = array('i', [0]*pat_len)
# i的起始位置为2的原因:
# i==0时,没有前缀,因此设置为0;
# i==1时,有前缀,但前缀元素只有1个,该前缀没有最长可匹配前缀子串,因此设置为0
i = 2 # 主串下标从2开始
j = 0 # 模式串下标从0开始
while i < pat_len: # 遍历主串
if pattern[i-1] == pattern[j]:
# 字符匹配则更新i和j
next_[i] = j + 1
i += 1
j += 1
else:
j = next_[j] # pattern[i] != pattern[j]时对j进行回溯
if j == 0 and pattern[0] != pattern[i-1]:
# 避免j==0时出现死循环
next_[i] = 0
i += 1
return next_
def kmp(target, pattern):
next_ = next_pattern(pattern) # 获取模式串的next_数组
i = 0 # 主串下标从0开始
j = 0 # 模式串下标从0开始
pat_len = len(pattern) # 模式串的长度
tar_len = len(target) # 主串的长度
pos = deque() # 保存模式串在主串中的多个匹配位置
while i < tar_len: # 遍历主串
if target[i] == pattern[j]:
# 字符匹配则更新i和j
i += 1
j += 1
if j == pat_len:
# 在主串中成功匹配到模式串
pos.append(i - pat_len) # 获得模式串在主串中的多个匹配位置
j = 0
else:
j = next_[j] # target[i] != pattern[j]时对j进行回溯
if j == 0 and target[i] != pattern[j]:
# 避免j==0时出现死循环
i += 1
return pos
if __name__ == '__main__':
target = 'abc;GTGTG566464kjlhlGTGTGSFDWD'
pattern = 'GTGTG'
pos = kmp(target, pattern)
print(pos)
"""
运行结果:
deque([4, 20])
Process finished with exit code 0
"""