web-dev-qa-db-ja.com

どの文字列検索アルゴリズムが実際に最速ですか?

私はしばらくの間、最速の文字列検索アルゴリズムで立ち往生してきましたが、多くの意見を聞きましたが、最終的にはわかりません。

最速のアルゴリズムはボイヤー・ムーアであると言う人もいれば、クヌース・モリス・プラットが実際には速いと言う人もいます。

私は両方の複雑さを調べましたが、それらはほとんど同じO(n+m)に見えます。最悪のシナリオでは、Boyer-MooreはO(m + 2 * n)を持つKnuth-Morris-Prattと比較してO(nm)の複雑さを持っていることがわかりました。ここで、n =テキストの長さ、m =パターンの長さ。

私が知る限り、ガリルルールを使用すると、ボイヤームーアは線形最悪のケースタイムになります。

私の質問、Over allこれは実際には最速の文字列検索アルゴリズムです(この質問には、Boyer-MooreとKnuth-Morris-Prattだけでなく、すべての可能なスティングアルゴリズムが含まれます)。

編集:this answer による

私が正確に探しているのは:

テキストTとパターンPを指定すると、P内のTのすべての外観を見つける必要があります。

また、PとTの長さは[1,2 000 000]およびプログラムは0.15秒未満で実行する必要があります。

KMPとRabin-Karpは問題の100%のスコアを取得するのに十分であることを知っていますが、ボイヤー・ムーアを試して実装したいと思っていました。このタイプのパターン検索に最適なのはどれですか。

27
vandamon taigi

実行する検索の種類によって異なります。それぞれのアルゴリズムは、特定のタイプの検索で特に効果的ですが、検索のコンテキストについては説明していません。

検索タイプに関する一般的な考え方を以下に示します。

  • ボイヤー・ムーア:パターンを事前に分析し、右から左に比較することで機能します。不一致が発生した場合、最初の分析を使用して、パターンをどのくらいシフトできるかを決定します。検索されるテキスト。これは、長い検索パターンで特に効果的です。特に、テキストの1つ1つの文字を読み取る必要がないため、直線的でない場合があります。

  • Knuth-Morris-Pratt:パターンも事前に分析しますが、パターンの最初の部分で既に一致したものはすべて再利用して、再一致する必要がないようにします。検索パターンに再利用可能なサブパターンが含まれる可能性が高くなるので、アルファベットが小さい(例:DNA塩基)場合、これは非常にうまく機能します。

  • Aho-Corasick:多くの前処理が必要ですが、多くのパターンで必要です。同じ検索パターンを何度も検索することがわかっている場合、パターンを分析する必要があるのは1回の検索ではなく1回だけなので、これは他の検索パターンよりもはるかに優れています。

したがって、CSでいつものように、全体的に最良に対する明確な答えはありません。それはむしろ、目前の仕事に適したツールを選択することの問題です。

最悪の場合の推論に関する別のメモ:その最悪の場合を作成するために必要な種類の検索を検討し、これらがあなたのケースに本当に関連しているかどうかを十分に検討してください。たとえば、Boyer-MooreアルゴリズムのO(mn)最悪の場合の複雑さは、それぞれが1文字だけを使用する検索パターンとテキストに由来します(aaaaaaaaaaaaaaaaaaaaaaaaを見つけるなど)-本当に高速である必要がありますか?そのような検索のために?

38
Frank

この質問に答えるのは少し遅れますが、_Z-Algorithm_は他の質問よりもはるかに高速だと思います。その最悪の場合の複雑さはO(m + n)であり、パターン/テキストの前処理は必要ありません。また、他のアルゴリズムと比較してコーディングが非常に簡単です。

次のように動作します。

たとえば、文字列_S ='abaaba'_があります。 z(i)i=0 to len(S)-1値を見つけます。説明に入る前に、いくつかの定義を最初に置きます。

z(i) =いいえ。 s(i)のプレフィックスと一致するSのプレフィックスの文字数。

s(i) = ithSのサフィックス。

以下は、_s = 'abaaba'_のs(i)の値です。

_s(0) = 'abaaba' = S
s(1) = 'baaba'
s(2) = 'aaba'
s(3) = 'aba'
s(4) = 'ba'
s(5) = 'a'
_

Z値はそれぞれ

_z(0) = 6 = length(S)
z(1) = 0
z(2) = 1
z(3) = 3
z(4) = 0
z(5) = 1
_

アルゴリズムの詳細については、次のリンクを参照してください。

http://codeforces.com/blog/entry/3107

https://www.youtube.com/watch?v=MFK0WYeVEag

ここで、前処理のオーバーヘッドなしにすべてのz値を見つけるにはO(N)が必要です。このロジックを使用して、与えられた文字列?

例で見てみましょう。パターン(P):aba、テキスト(T):aacbabcabaad

これをP $ Tの形式で入力します。 (_$_-パターンにもテキストにも表示されない任意の文字。しばらくして_$_の重要性に気づくでしょう。)

_P$T_ = _aba$aacbabcabaad_

len(P) = 3です。

_P$T_のすべてのz値は

_z(0) = 16 = len(P$T)
z(1) = 0
z(2) = 1
z(3) = 0
z(4) = 1
z(5) = 1
z(6) = 0
z(7) = 0
z(8) = 2
z(9) = 0
z(10) = 0
z(11) = 3
z(12) = 0
z(13) = 1
Z(14) = 1
Z(15) = 0
_

ここでz(i) = len(P)になります。 _Ans = 11._したがって、パターンはAns-len(P)-1 = _7_にあります。 _-1_は_$_文字用です。

では、なぜ_$_またはそのような特殊文字が重要なのでしょうか。 _P = 'aaa'_および_T = 'aaaaaaa'_を検討してください。特殊文字がない場合、すべてのz(i)には増分値があります。以下の式を使用して、テキスト内のパターンの位置を見つけることができます。

条件:z(i)> = len(P)および位置:Ans-len(P)。しかし、この場合の状態は少しトリッキーで混乱を招きます。個人的には特殊キャラクターのテクニックを使う方が好きです。

1
SohamC