KMP算法简单介绍
KMP是三位大牛:D.E.Knuth、J.H.Morris和V.R.Pratt同时发现的。其中第一位就是《计算机程序设计艺术》的作者!!
算法主要的作用就是匹配字符串,我们设置T为主串,P为子串。配置在T中是否包含P的话,我们当然可以通过暴力匹配的方式来实现。python代码实现非常简单如下。
暴力匹配方式
def naive_match(s, p):
m = len(s); n = len(p)
for i in range(m-n+1):#起始指针i
if s[i:i+n] == p:
return True
return False
KMP算法
算法最最重要的就是找到前后缀最长公共元素,以‘ABCDABD’为例:
- "A"的前缀和后缀都为空集,共有元素的长度为0;
- "AB"的前缀为[A],后缀为[B],共有元素的长度为0;
- "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;
- "ABCD"的前缀为[A, AB,ABC],后缀为[BCD, CD, D],共有元素的长度为0;
- “ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A”,长度为1;
- “ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB”,长度为2;
- "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD,DABD, ABD, BD, D],共有元素的长度为0。
则最终我们的‘ABCDABD’前后缀匹配值为[0,0,0,0,1,2,0]
KMP算法小例子
- 设T为‘DABABC’,P为‘ABAC’
- ABAC的前后缀值为[0,0,1,0],所以保持j不变k变为P的1位置
- 那如何理解next呢?
next原理
1.由"next[j] == k;"这个条件,我们可以得到A1子串 == A2子串(根据next数组的定义,前后缀那个)。
2.由 “next[k] == 绿色色块所在的索引;” 这个条件,我们可以得到B1子串 == B2子串。
3.由 “next[绿色色块所在的索引] == 黄色色块所在的索引;” 这个条件,我们可以得到C1子串 == C2子串。
4.由1和2 (A1 == A2,B1 == B2) 可以得到 B1 == B2 == B3。
5.由2和3 (B1 == B2, C1 == C2) 可以得到 C1 == C2 == C3。
6.B2 == B3可以得到C3 == C4 == C1 == C2
- 如果j位置T[i]==P[j] ,则next[j]=k+1
- 如果不匹配,那么就要找到更小的前后缀子串,也就是A1的前缀与A2的后缀部分哪些部分是匹配的。
- A1==A2 推断出要找的是A1的前后缀那部分是匹配的,那么只需找到next(k)即可
- 就是在不匹配的情况下,令k = next(k),并迭代即可。
代码
def get_next(P):
next_list = [0 for i in range(len(P))]
j,k,next_list[0] = 0,-1,-1
while j<len(P)-1:
if k ==-1 or next_list[j] == next_list[k]:
j += 1
k += 1
next_list[j]=k
else:
k = next_list[k]
return next_list
def my_kmp(T,P):
T_len = len(T)
P_len = len(P)
next_P = get_next(P)
#主串位置为i,模式串为j
i,j = 0,0
while i< T_len and j < P_len:
if j == -1 or T[i]==P[j]:
i +=1
j+=1
else:
j = next_P[j]
if j == P_len:
return i-j
else:
return -1
if __name__ == '__main__':
T = 'adsaafaaffdsaf'
P = 'afaaf'
print(get_next(P))
print(my_kmp(T,P))