KMP演算法(python實現)
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 (短字串在第五個字元第一次匹配錯誤)
ab
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