KMPアルゴリズムの精解とPython版のコード例


KMPアルゴリズムは古典的な文字列整合アルゴリズムであり、文字列Sからモード文字列Mを検索する問題を解決する。アルゴリズム名は、発明者Knuth、Morris、Pratに由来する。
文字列SからM,Sの長さls,Mの長さlmを検索し(ls>lm)とする。
シンプルな文字列の検索方法
文字列Sの最初の文字からMとの比較を開始し、もし一致が失敗したら。次の文字から、再比較します。第(ls-lm)文字の指導を行います。
この方法は考えやすく、理解しやすく、効率が高くない。
問題は毎回マッチングが失敗した後、移動の歩調が1に固定されていますが、もっと大きく歩けるということです。
KMPの文字列の検索方法
パターンストリングの連続する文字列M[0,i]およびi<lmは、文字列Sとのマッチングに成功したと仮定する。あいにくi+1文字目が失敗しました。どうすればいいですか?文字を移動して、もう一度来ますか?もちろんよくないです。それは素朴なコースです。転んだところから歩き続けてもいいですか?
文字列M[0-i]がマッチした以上、この文字列から文章を作ります。くりを一つあげる     
S番号
j
j+1
 j+2
j+3
j+4
j+5
 j+6
j+7
です。
S列
a.
b
c
a.
b
c
d
e
です。
M串
a.
b
c
a.
b
d
M番号
0
1
2
3
4
5
このとき、M列の5番目の文字にマッチできませんでした。最初の4文字がマッチしました。
転倒したところから出発すると、M[0,4]のサブシリアルM[0,k]==S[j+4-k,j+4]が必要です。
M[0,4]==S[j,  j+4は文字列S[j+4-k,j+4]==M[4-k,4]があります。以上、M[0,k]==M[4-k,4]があります。
このようなkが存在しないなら、素直で素朴です。
上の表からは、次のマッチはM列をj+3位置に移動するだけで、j+5からマッチングすればいいと直感的に見られます。すでにマッチングした文字列M[0,4]の中で一番長いサブストリング(M[0,1]==M[3,4]があることが分かりやすいです。これが問題の鍵です。
したがって、KMPのコア部分は、各サブストリングのkを計算することである。
実例
まず文字列のシンプルなマッチングを見てみます。
文字列sを固定して、パターン列pはsの一番左から揃え始めます。配置部分が完全に同じであれば、マッチングに成功します。失敗したら、パターン列p全体を右に1桁移動して、配置部分を引き続きチェックします。このように繰り返します。

#     
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アルゴリズムについて、一番いいのは阮一峰の「文字列一致のKMPアルゴリズム」です。
実際には、パターンストリップpを前処理して、前のサフィックスの部分マッチングテーブルを得て、私達は既知の情報を利用して、右のシフトができるビット数を算出することができます。つまり、kmp=シンプルなマッチング+移動ビットです。
もっと細かいところは阮一峰の文章を見てください。ここでは展開しません。
pythonのコードを以下に示します。

#KMP 
def kmp_match(s, p): 
 m = len(s); n = len(p) 
 cur = 0#    cur 
 table = partial_table(p) 
 while cur<=m-n: 
  for i in range(n): 
   if s[i+cur]!=p[i]: 
    cur += max(i - table[i-1], 1)#       ,        1 1    ,         
    break 
  else: 
   return True 
 return False 
 
#      
def partial_table(p): 
 '''''partial_table("ABCDABD") -> [0, 0, 0, 0, 1, 2, 0]''' 
 prefix = set() 
 postfix = set() 
 ret = [0] 
 for i in range(1,len(p)): 
  prefix.add(p[:i]) 
  postfix = {p[j:i+1] for j in range(1,i+1)} 
  ret.append(len((prefix&postfix or {''}).pop())) 
 return ret 
 
print naive_match("BBC ABCDAB ABCDABCDABDE", "ABCDABD") 
print partial_table("ABCDABD") 
print kmp_match("BBC ABCDAB ABCDABCDABDE", "ABCDABD")