1. 名词和定义
字符串:strs,例如'aabcaab'
前缀:'a'或'aabc'等strs[0:k], k<len(strs)
后缀:strs[i:len(strs)], i>0
注:前缀和后缀的最大长度是要小于strs的长度的
next数组:next[0]=0, next[1] : strs[0:1](也即是strs的第一个元素)的前缀和后缀公共元素的最大长度=0
next[k]: strs的前k个元素组成字符串的前缀和后缀公共元素的最大长度
next[0] | next[1] | next[2] | next[3] | next[4] | next[5] | next[6] | next[7] |
0 | 0 | 1 | 0 | 0 | 1 | 2 | 3 |
2. next数组的性质和实现
(1)next[0] = 0恒成立,next[i] : 表示i个元素的字符串的前缀和后缀公共元素的最大长度(和我们表示数组的方式不同,next从1开始,才有意义)
(2)len(next) = len(strs)+1(也有资料说next是不要最后一项的)
(3)若i表示字符串字符下标,则:令j = next[i], 若 str[i] == str[j] 则,next[i+1] = next[i] + 1; 否则 j = [next[next[i]]], 直到等式成立。根据这个可以求出next的值。代码如下:
def getNextList(strs):
n = len(strs)
nextlist = [0,0]
j=0
for i in range(1,n):
while j>0 and strs[i]!=strs[j]:
j = nextlist[j]
if strs[i] == strs[j]:
j += 1
nextlist.append(j)
return nextlist
3. KMP算法
在长字符串s中找到短字符串p的位置。暴力匹配法的思想是: 找到s中第一个与p[0]相等的字符位置i,然后依次比较s[i:len(p)+i]与p,如果有一个字符不等,则返回i+1的位置,与p[0]比较;然后重复上述过程,直到遍历完整个s。
KPM算法与上述算法区别就是上面红字部分。如果s[i] = p[0],但是s[i+k] != p[k], 这时,比较s[i+k: ]和p[next[k]: ]的值(相当于p右移了k-next[k]位)。如果相等,则返回i+k-next[k],否则重复上述过程。
ababaababbc (长字符串s)
ababb (短字符串在第五个字符第一次匹配错误)
ababb (比较p[2: ]和s[4: ],一直进行下去)
def KMP(s,p):
'''
:param s: 原始字符串
:param p: 需要匹配的字符串
:return: 匹配的位置向量
'''
n = len(s)
m = len(p)
next_list = getNextList(p)
res = []
j = 0
for i in range(n):
while s[i] != p[j] and j > 0:
j = next_list[j]
if s[i] == p[j]:
j += 1
if j == m:
res.append(i-m+1)
j = next_list[j]
return res
4. next的优化
考虑下面情况:
abacaddaaa (原字符串s)
abab (需要匹配的字符串p,第一次在i=3处匹配失败,右移i-next[3]=2个单位)
abab (比较p[next[3]]=p[1]和'c',也匹配失败)
实际上,因为p[3] = p[next[3]],所以当p[3]匹配失败时,p[next[3]] 肯定也匹配失败,因此可以将next数组按照下述规则进行优化:
if p[j] == p[next[j]]: next[j] = next[next[j]] 对于任意的j>=1
def getNextList(strs):
n = len(strs)
alist = [0,0]
k = 0
for i in range(1,n):
while strs[i] != strs[k] and k != 0:
k = alist[k]
if strs[i] == strs[k]:
k += 1
if strs[i] == strs[alist[i]]: #优化添加的代码
alist[i] = alist[alist[i]]
alist.append(k)
return alist