KMP 算法详解
什么是 KMP 算法
- KMP 算法是一种改进的字符串匹配算法,即可以快速的从主串中找到子串的算法,由 D.E.Knuth,J.H.Morris 和V.R.Pratt 提出的,因此人们简称为
KMP算法。 - KMP 算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。
KMP 算法实现思路
- 根据模式串计算一个
next
数组。这个数组的每个元素表示字符匹配失败时,应该将模式串的下标移动到那个位置。 - 检索匹配,当某个字符匹配失败时,然后根据
next
数组的值更新模式串匹配的下标位置。这样就会快速完成匹配检索。
KMP 算法详解
计算next
数组
-
核心思想:
求解最长相等前后缀的长度。 -
next
数组表示的含义:
当使用模式串去匹配时,字符不匹配时,应该将模式串匹配的位置回溯到某个位置。 -
next
数组的定义:
next
数组的长度等于模式串的长度,next
中的每个元素的值是相等前后缀的最长长度。 -
理解
next
数组中的值:- 当前元素匹配失败,那么当前元素之前的模式子串是匹配的。
- 在匹配的子串中,找出所有相等的前后缀,取最长的前后缀子串长度作为当前元素的
next
值。
根据以上定义,假设模式串为
ababaaab
,那么模式串的next
数组值如下:下标 0 1 2 3 4 5 6 7 模式串 a b a b a a a b next -1 0 0 1 2 3 1 1 其中
next[3] = 1
表示在下标 3 位置匹配失败,这时可以将位置移动到 1,即子串aba
的最长相等前后缀a
的长度。你可以按照此方法类推其他位置匹配失败时的值。 -
代码实现。
-
根据模式串计算
next
数组。def get_next(t: str): """求取 next 数组。 :param t: 输入的模式串 :return: 返回 next 数组。 用法: >>> t="ababaaab" >>> get_next(t) [-1, 0, 0, 1, 2, 3, 1, 1] """ # 模式串的长度 l = len(t) # 初始化 next 数组,第一个元素默认使用 -1 next = [-1] * l # 模式串的下标 j = 0 # 相等前后缀的最长值 k = -1 # 计算。因为每一步赋值都是在 j++ 之后,所以 j 要控制在 l-1 之内 while j < l - 1: # 当相等的前后缀长度为 -1 或者 j 位置和 k 位置的字符相等时, 进行自增,并填写 next 的值。 if k == -1 or t[j] == t[k]: j += 1 k += 1 next[j] = k # 不相等时,更新 k 的值,这一步很巧妙。不匹配时,要移动到 k 位置继续比较,这时需要将 k 更新为 k 位置的最长前后缀,然后继续比较,因为 K 前边的字符肯定是匹配的。 else: k = next[k] return next
-
优化改进。
用上面的模式串例子说明怎样优化改进。下面是求得的next
数组值:下标 0 1 2 3 4 5 6 7 字符 a b a b a a a b next -1 0 0 1 2 3 1 1 当在 2 位置时匹配失败,那么应该回到 0 位置继续匹配,但 0 位置的字符与 2 位置的字符相同, 那么移动到 0 位置比较,肯定也不匹配,这时需要继续移动,所以可以直接将 2 位置的的值更新为 0 位置的 next 值,相当于回溯位置的值如果与当前位置的字符相同,那么就可以将回溯值直接调整到回溯位置的
next
值。下面是代码实现:
def get_next_val(t: str): """优化 next 数组 :param t: 模式串 :return: 返回优化后的 next 数组。 用法: >>> t="ababaaab" >>> get_next_val(t) [-1, 0, -1, 0, -1, 3, 1, 0] """ l = len(t) nextval = [-1] * l # 字符串的起始位置 j = 0 # 最长前后缀长度 k = -1 while j < l - 1: if k == -1 or t[j] == t[k]: j += 1 k += 1 if t[j] == t[k]: nextval[j] = nextval[k] else: nextval[j] = k else: # 要移动到 k 位置进行匹配,那么 k 位置之前的最长相等前后缀就是现在匹配时的前后缀初始值。 k = nextval[k] return nextval
-
检索
求出了next
数组,检索就很容易了。下面是具体的代码实现:
def KMP_index(s: str, t: str):
"""KMP 算法实现,从 s 中查找模式串 t。
:param s: 输入的字符串。
:param t: 模式串。
:return: 返回匹配的模式串下标。
用法:
>>> s = "Who do created KMP algorithm? It is very beautiful."
>>> t = "? It"
>>> KMP_index(s, t)
28
"""
nextval = get_next_val(t)
s_len = len(s)
t_len = len(t)
i = 0
j = 0
while i < s_len and j < t_len:
if j == -1 or s[i] == t[j]:
i += 1
j += 1
else:
# 字符未匹配,则移动模式串的下标。
j = nextval[j]
return i - t_len if j >= t_len else -1
上述代码就是 KMP 算法的实现,如果字符串的长度为m
,模式串的长度为n
,那么这个算法的时间复杂度为
O
(
m
+
n
)
O(m+n)
O(m+n),这是因为字符串仅仅遍历了一次,求next
数组的时间复杂度是
O
(
n
)
O(n)
O(n),所以总的时间复杂度为
O
(
m
+
n
)
O(m+n)
O(m+n)。