C++の実装されたstring::find()
が KMPアルゴリズム を使用せず(そしてO(N + M)
で実行されず)、O(N * M)
で実行される理由?それはC++ 0xで修正されていますか?現在の検索の複雑さがO(N * M)
でない場合、それは何ですか?
では、どのアルゴリズムがgccに実装されていますか?それはKMPですか?そうでない場合、なぜですか?私はそれをテストしました、そして実行時間はそれがO(N * M)
で実行されることを示します
C++の実装されたstring :: substr()がKMPアルゴリズムを使用せず(そしてO(N + M)で実行されない)、O(N * M)で実行されるのはなぜですか?
検索する必要がなく、線形時間で実行する必要があるfind()
ではなく、substr()
を意味していると思います(結果を新しい文字列にコピーする必要があるため)。
C++標準は実装の詳細を指定せず、場合によっては複雑さの要件のみを指定します。 _std::string
_操作の複雑さの要件は、size()
、max_size()
、_operator[]
_、swap()
、c_str()
のみです。およびdata()
はすべて一定時間です。他のものの複雑さは、使用しているライブラリを実装した人の選択によって異なります。
KMPのようなものよりも単純な検索を選択する最も可能性の高い理由は、追加のストレージを必要としないようにするためです。検出される文字列が非常に長く、検索する文字列に部分一致が多数含まれている場合を除いて、割り当てと解放にかかる時間は、余分な複雑さのコストよりもはるかに長くなる可能性があります。
それはc ++ 0xで修正されていますか?
いいえ、C++ 11は_std::string
_に複雑さの要件を追加しません。また、必須の実装の詳細も追加しません。
現在のsubstrの複雑さがO(N * M)でない場合、それは何ですか?
これは、検索する文字列に長い部分一致が多数含まれている場合の最悪の場合の複雑さです。文字の分布が適度に均一である場合、平均の複雑さはO(N)
に近くなります。したがって、最悪の場合の複雑さがより優れたアルゴリズムを選択することで、より一般的なケースをはるかに遅くすることができます。
std::string::substr()
が線形アルゴリズムを使用していないという印象はどこから得られますか?実際、あなたが引用した複雑さを持つ方法で実装する方法を想像することさえできません。また、関連するアルゴリズムはそれほど多くありません。この関数がそれ以外のことをしていると思われる可能性はありますか? std::string::substr()
は、最初の引数から始まり、2番目のパラメーターで指定された文字数または文字列の最後までの文字を使用して、新しい文字列を作成するだけです。
複雑さの要件がないstd::string::find()
、または実際にO(n * m)比較を実行できるstd::search()
を参照している可能性があります。ただし、これにより、実装者は、理論的に最も複雑なアルゴリズムと、追加のメモリを必要としないアルゴリズムのどちらかを自由に選択できます。特に要求されない限り、任意の量のメモリの割り当ては一般に望ましくないため、これは合理的なことのように思われます。
参考までに、gcc/libstdc ++とllvm/libcxxの両方でstring :: findが非常に遅くなりました。私はそれらの両方をかなり大幅に改善しました(場合によっては約20倍)。新しい実装を確認することをお勧めします。
GCC:PR66414最適化std :: string :: find https://github.com/gcc-mirror/gcc/commit/fc7ebc4b8d9ad7e2891b7f72152e8a2b7543cd65
LLVM: https://reviews.llvm.org/D27068
新しいアルゴリズムはより単純で、memchrおよびmemcmpの手動で最適化されたアセンブリ関数を使用します。
CLRSの本を見てみましょう。第3版の989ページには、次の演習があります。
パターンPとテキストTが、d-aryアルファベットƩからそれぞれ長さmとnのランダムに選択された文字列であると仮定します。d = {0; 1; ...; d}、ここでd> = 2です。ナイーブアルゴリズムの4行目の暗黙的なループによって行われる文字間の比較の予想数が
このループのすべての実行にわたって。 (ナイーブアルゴリズムは、不一致が見つかるか、パターン全体に一致すると、特定のシフトの文字の比較を停止すると想定します。)したがって、ランダムに選択された文字列の場合、ナイーブアルゴリズムは非常に効率的です。
NAIVE-STRING-MATCHER(T,P)
1 n = T:length
2 m = P:length
3 for s = 0 to n - m
4 if P[1..m] == T[s+1..s+m]
5 print “Pattern occurs with shift” s
証明:
単一シフトの場合、1 + 1/d + ... + 1/d^{m-1}
比較を実行することが期待されます。次に、合計式を使用して、有効なシフトの数(n - m + 1
)を掛けます。 □
C++標準は、substr
(またはM*N
の複雑さで参照する可能性が最も高いfind
を含む他の多くの部分)のパフォーマンス特性を規定していません。
これは主に、言語の機能側面を指示します(たとえば、非レガシーのsort
関数などのいくつかの例外を除く)。
実装は、バブルソートとしてqsort
を自由に実装することもできます(ただし、嘲笑されて廃業する可能性がある場合に限ります)。
たとえば、C++ 11のセクション21.4.7.2 basic_string::find
には7つの(非常に小さい)サブポイントしかなく、それらのnoneはパフォーマンスパラメータを指定します。
C++ライブラリに関する情報はどこで入手できますか?あなたが意味するならstring::search
そして実際にはKMPアルゴリズムを使用していないので、検索を続行する前に部分一致テーブルを作成する必要があるため、そのアルゴリズムは一般に単純な線形検索よりも高速ではないためです。
複数のテキストで同じパターンを検索する場合。パターンテーブルは1回だけ計算する必要があるため、BoyerMooreアルゴリズムは適切な選択ですが、複数のテキストを検索する場合は複数回使用されます。ただし、1つのテキストに1回だけパターンを検索する場合は、テーブルの計算のオーバーヘッドとメモリの割り当てのオーバーヘッドが遅くなりすぎて、std :: string.find(....)が勝ってしまいます。メモリを割り当てず、オーバーヘッドがないためです。 Boostには複数の文字列検索アルゴリズムがあります。 1つのテキストでの単一パターン検索の場合、BMはstd :: string.find()よりも1桁遅いことがわかりました。私の場合、同じパターンの複数のテキストを検索する場合でも、BoyerMooreがstd :: string.find()よりも高速になることはめったにありませんでした。これがBoyerMooreへのリンクです BoyerMoore