記事ディレクトリ
十分に理解します
文字列照合アルゴリズムでは、KMPアルゴリズムはほぼO(N)の複雑さを実現できます。重要なのは、メインポインターのバックトラックを排除することです。これにより、多くの時間を節約できます。
たとえば、一致させたい場合abcdabce
、abce
ブルートフォースアルゴリズムを次の表に示します。4文字を比較する必要があるたびに、合計5回です。
a | b | c | d | a | b | c | e | |
---|---|---|---|---|---|---|---|---|
1 | a | b | c | e | ||||
2 | a | b | c | e | ||||
3 | a | b | c | e | ||||
4 | a | b | c | e | ||||
√ | a | b | c | e |
d
ただし、これがまったく含まれていないことが一目でわかるabce
ので、他の情報を保存できれば、一度にスキップできるかもしれませんd
。
a | b | c | d | a | b | c | e | |
---|---|---|---|---|---|---|---|---|
1 | a | b | c | e | ||||
√ | a | b | c | e |
しかし、それ以上ジャンプできない場合もあります。たとえば、一致abcabcabc
さcab
、より良い解決策はおおよそ次のとおりです。
a | b | c | a | b | c | a | b | c | |
---|---|---|---|---|---|---|---|---|---|
1 | c | a | b | ||||||
√ | c | a | b |
したがって、問題の核心は、なぜ上記のケースを直接スキップできるのかd
、そして次のケースは正確に2つしかスキップできないのかということです。
要約すると、2つのルールが見つかりました。txtを長いテキスト、txtでstrを見つける必要があり、現在の比較の文字をchとすると、2つの簡単なルールがあります。
chはstrにありません | strこのchをスキップ |
chはたまたまstr[0]です | strはこのchの位置に転送されます |
次に、chがstrにあるが、str [0]にない場合、何を考慮する必要がありますか?
もちろん、直接スキップすることはできません。strにはabababc
、それらからababc
、最善の解決策は次のようになります。
a | b | a | b | a | b | c | |
---|---|---|---|---|---|---|---|
1 | a | b | a | b | c | ||
√ | a | b | a | b | c |
つまり、ababc
このような文字列a
の場合、位置が異なるため、新しい文字列を取得するa
と、異なる決定が行われます。
下の図では、円は現在の一致を表し、矢印は新しいキャラクターを表し、矢印は次のジャンプ位置を指しています。感嘆符は特定の文字ではないことを意味します。
ここを見るのは理解の感覚ではなく、これはいわゆるステートマシンであり、このステートマシンの構築プロセスはtxtとは何の関係もありません。つまり、txtを照合する前にstrで自己照合を行うだけで、このようなステートマシンを取得できます。
ステートマシンの状態は、実際には一致する文字列内のポインタ位置を表します。に戻るとは、ポインタが0を指すことを意味します。前進すると、ポインタは1ずつ増加します。ababがaを受け取り、のステップに戻ると、 abaは、次の表に示すように、ポインタが5から3にロールバックすることを意味します。
a | あちらへ | aba | アバブ | ababc | |
---|---|---|---|---|---|
a | 0 | 0 | 0 | 3 | |
b | 0 | 0 | 0 | 0 | |
c | 0 | 0 | 0 | 0 | 成功 |
他の | 0 | 0 | 0 | 0 |
いわゆるステートマシンが実際にはマトリックスであることに突然気づきましたか。
次にやらなければならないことは、この状態行列を生成することです。
大まかな実装
または、txtからstrを見つけることを検討してください。最初のステップは、strの状態行列を確立することです。
test = "ababcdabadc" #python中str是关键字,所以改个名
length = len(test)
#创建用于存储状态的字典
status = {
s:[0 for _ in range(length)] for s in set(test)}
for ch in status:
for i in range(length):
for j in range(i+1):
if test[i-j:i]+ch == test[0:j+1]:
status[ch][i] = j+1
得る
b [0, 2, 0, 4, 0, 0, 0, 8, 0, 4, 0]
d [0, 0, 0, 0, 0, 6, 0, 0, 0, 10, 0]
c [0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 11]
a [1, 1, 3, 1, 3, 1, 7, 1, 9, 1, 1]
文字列ababcdabadc
の場合、最初のポインタは0です。この時点a
でポインタが存在する場合、ポインタはその文字列にジャンプし、現在の状態a[0]=1
を示します。この時点で別のポインタがある場合、ポインタはにジャンプします。この時点での状態を示します。a
b
b[1]=2
ab
strの状態行列を作成した後、文字列比較を実行すると非常に便利です。
txt = "ababcdabasdcdasdfababcdabadc"
test = "ababcdabadc"
KMP(txt,test)
def KMP(txt,test):
status = setStatus(test)
length = len(test)
keySet = set(status.keys())
match = []
j = 0
for i in range(len(txt)):
s = txt[i]
j = status[s][j] if s in keySet else 0
if j==length:
match.append(i-length+1)
return match
def setStatus(test):
length = len(test)
#创建用于存储状态的字典
status = {
s:[0 for _ in range(length)] for s in set(test)}
for ch in status:
for i in range(length):
for j in range(i+1):
if test[i-j:i]+ch == test[0:j+1]:
status[ch][i] = j+1
return status
マッチング行列の最適化
一般にaaaaaaa
、狂気の文字列以外の文字列の一致行列はスパースです。つまり、多くの役に立たない比較を行います。したがって、そのマッチング行列の解法プロセスを大まかに最適化することができ、少なくとも最も外側のループを削除することができます。
def setStatus(test):
length = len(test)
#创建用于存储状态的字典
status = {
s:[0 for _ in range(length)] for s in set(test)}
for i in range(length):
for j in range(i+1):
if test[i-j:i] == test[0:j]:
ch = test[j]
status[ch][i] = j+1
return status
ループは2層しか残っていないのでさわやかに見えますが、真実を知らないメロンを食べる人はまだ気にしていますO(N 2)O(N ^ 2)O (女性)2)複雑さ。よりリフレッシュするために、マッチングマトリックスの特性を調べてみましょう
b [0, 2, 0, 4, 0, 0, 0, 8, 0, 4, 0]
d [0, 0, 0, 0, 0, 6, 0, 0, 0, 10, 0]
c [0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 11]
a [1, 1, 3, 1, 3, 1, 7, 1, 9, 1, 1]
まず、ほとんどのゼロ以外の値がインクリメントされます。d
の非ゼロ値、 6,11
。c
の非ゼロ値など5,11
。そして、小さい値があると、この値は前に表示されている必要があります。たとえばb[9]=4
、この4は前に表示されています。
それを原則としてとる索引降低,则索引必然重复
と、明らかに0
、1
この原則に含めることもできますがb,c,d
、最初に真ん中の0が表示されます。また、最小値は1しかないa
ため、真ん中に0はありません。a[0]=1
または、、またはa[i]
これi+1
までに発生した値。
この場合、長さのN
文字列の場合、後方照合のためにその文字列から前の文字を抽出str
できます。たとえば、最初に一致して正常に一致する位置のセットを取得し、次にこれらの位置を一致させ、以下同様に一致する位置が0の位置になるまで続けます。str
M
"ababcdabadc"
a
ab