KMP字符串匹配算法的实现
暴力查找
- 这是最简单的一种字符串匹配算法:
- 使用一个指针
i
跟踪目标文本txt
, 使用指针j
跟踪模式字符串pat
, 将j
置为0且不断增大,直到找到一个不匹配的字符或是模式字符串结束为止。 - 如果没有找到匹配,那么将指针
i
向后移动一位,继续进行匹配操作
- 这种算法的时间复杂度是O(M*N)
def brute_force(txt, pat):
for i in range(len(txt) - len(pat) + 1):
for j in range(len(pat)):
if txt[i+j] != pat[j]:
break
if j == len(pat) - 1: # 找到匹配的位置
return i
return -1 # 未找到匹配的位置
暴力查找算法最坏的情况需要查找 M*N 次,即对于每一个目标文本中的字符,都会匹配到模式字符串中的最后一个字符
KMP字符串匹配算法
-
在暴力查找算法中,当出现不匹配的情况时,会回退目标文本的指针以进行下一次匹配,但是在这种情况下,我们已经知道了一部分文本的内容(在匹配失败之前这些文本已经和模式字符串匹配成功),我们可以利用这个信息来避免文本指针
i
的回退,从而减少时间复杂度 -
例子
暴力算法 pattern B A A A A A A A A i | txt A B A A A A B A A A A A A B A A A A A B A A A A A B A A A A A B A A A A A B A A A A A B A A A A A --> 匹配成功
在第5个字符(以0为起始位)匹配失败之后,暴力算法会将
i
回退到2,再次进行匹配。
但是我们可以发现这里其实并不需要回退,因为i
前面4位的字符都是 A ,均不能与 B 匹配,另外,文本字符串txt
中i
位的字符 B 与模式字符串pat
的首位 B。
因此,我们可以直接将i
后移一位,以比较文本字符串中的下一个字符和模式字符串中的第2个字符:i->i+1 | | txt A B A A A A B A A A A A A B A A A A A B A A A A A --> 匹配成功
-
KMP 算法的主要思想就是提前判断如何重新开始匹配,而这种判断只取决于模式字符串本身,与目标文本没有关系
-
字符串的前缀、后缀
- 对于字符串 A 和 B ,如果存在 A = BS, 其中S是一个任意的非空字符串,那么我们就称 B 是 A 的前缀
- 对于字符串 A 和 B ,如果存在 A = SB,其中S是一个任意的非空字符串,那么我们就称 B 是 A 的后缀
- 例子:
- 字符串 ABABCB
- 前缀: {'A', 'AB', 'ABA', 'ABAB', 'ABABC'}
- 后缀: {'B', 'CB', 'BCB', 'ABCB", 'BABCB'}
-
KMP算法是通过什么样的规则来判断如何重新匹配的?
-
核心:部分匹配表(Partial Match Table)数组
-
对于一个字符串 'ABABABCA', 它的PMT表如下
char A B A B A B C A index 0 1 2 3 4 5 6 7 value 0 0 1 2 3 4 0 1 -
PMT数组中值的定义:
- 字符串的前缀集合与后缀集合的交集中最长元素的长度。
- 例子: 对于字符串 ABAB
- 前缀集合: {'A', 'AB', 'ABA'}
- 后缀集合: {'B', 'AB', 'BAB'}
- 前缀集合与后缀集合的交集是 'AB', 长度是2,因此PMT数组中的值是2
-
-
KMP算法是如何通过PMT表来进行重新匹配的?
-
例子: 在文本字符串 'ABCAABABABABCABA' 中查找模式字符串 'ABABABCA'
i=6 | A B A B A B A B A B C A B A A B A B A B C A | j=6
在
i
处失配,意味着 文本字符串中 i 指针之前的 PMT[j −1] 位就一定与模式字符串的第 0 位至第 PMT[j−1] 匹配, 在这里是 ABABAB。
这个字符串的前缀集合是{'A', 'AB', 'ABA', 'ABAB', 'ABABA'}, 后缀集合是{'B', 'AB','BAB', 'ABAB', 'BABAB'}, 它们的交集是'ABAB'。
这就说明文本字符串中,i
所在位置之前的4位和模式字符串的0到4位是相同的,因此我们可以省略掉这些字符串的比较----保持i
= 6 不动, 将j
指向模式字符串的PMT[j-1] (此处为4)位即可i=6 | A B A B A B A B A B C A B A A B A B A B C A | j=4
-
代码实现:
在第j
位失配时,我们实际上是取第j-1
位的PMT值,为了方便,我们可以将PMT数组向后偏移一位,得到一个数组,称为next
数组:char A B A B A B C A index 0 1 2 3 4 5 6 7 value 0 0 1 2 3 4 0 1 next -1 0 0 1 2 3 4 0 def kmp(txt, pat): i = j = 0 while i < len(txt) and j < len(pat): if j == -1 or txt[i] == pat[j]: # 首位失配或者匹配成功,i、j都向后移动一位 i, j = i + 1, j + 1 else: j = next[j] if j == len(pat): return i - j else: return -1
-
next
数组的生成:- 求next数组的过程可以看成字符串匹配的过程:
- 从模式字符串的第一位(不包括第0位)开始对自身进行匹配运算。 在任一位置,能匹配的最长长度就是当前位置的next值。如下图所示。
pmt[0] = 0 => next[1] = 0 i | A B A B A B C A A B A B A B C A pmt[1] = 0 => next[2] = 0 | j i | A B A B A B C A A B A B A B C A pmt[2] = 1 => next[3] = 1 | j i | A B A B A B C A A B A B A B C A pmt[3] = 2 => next[4] = 2 | j i | A B A B A B C A A B A B A B C A pmt[4] = 3 => next[5] = 3 | j i | A B A B A B C A A B A B A B C A pmt[5] = 4 => next[6] = 4 | j i | A B A B A B C A A B A B A B C A pmt[6] = 0 => next[7] = 0 | j
- 代码实现:
def get_next(pat) next = [-1] i, j = 0, -1 while i < len(pat): if j == -1 or pat[i] == pat[j]: i, j = i+1, j+1 next.append(j) else: j = next[j] return next
- 求next数组的过程可以看成字符串匹配的过程:
-
-
网友评论