2


0

パフォーマンスを向上させるには、挿入ソートを使用するか、ヒープを構築する必要がありますか。

構造体の大きい(100,000要素)順序付けされたベクトルがあります(演算子<順序付けを提供するために多重定義されています)。

std::vector < MyType > vectorMyTypes;
std::sort(vectorMyType.begin(), vectorMyType.end());

私の問題は、ソート順を維持しながらこれらのベクトルに新しい要素を追加するときにパフォーマンスの問題が発生していることです。 現時点では、次のようにしています。

for ( a very large set )
{
    vectorMyTypes.push_back(newType);
    std::sort(vectorMyType.begin(), vectorMyType.end());

    ...

    ValidateStuff(vectorMyType); // this method expects the vector to be ordered
}

この例はさまざまな方法で最適化できることがわかっているので、これは私たちのコードが正確に示すものではありません。

パフォーマンスを向上させるには、2つの選択肢があると思います。

  1. `std :: sort`の代わりに(手作り?)_insertion sort_を使用して 並べ替えのパフォーマンスを改善します(部分的に並べ替えられたベクトルへの挿入並べ替えは、めちゃくちゃ速いです)

  2. std :: make_heap`と std :: push_heap`を使用してヒープを作成し、 ソート順を維持する

私の質問は:

  • 挿入ソートを実装する必要がありますか? Boostに何かありますか それはここで私を助けることができますか?

  • ヒープの使用を検討しますか? どうすればいいですか。

'' '' '

編集:

ご回答ありがとうございます。 私が示した例は最適とは言い難いものであり、それが今私のコードに持っているものを完全に表すものではないことを私は理解しています。 それは私が経験していたパフォーマンスのボトルネックを説明するためだけにありました。

https://stackoverflow.com/users/13005/ [Steve]のおかげで、最も簡単な回答が最もよく得られることが多く、おそらく私が最も明白な解決策を盲目にしていたのは、私が問題を過度に分析したためです。 私はあなたが事前注文されたベクトルに直接挿入するために概説したきちんとした方法が好きです。

私がコメントしたように、私は今のところベクトルを使うことに制限されているので、std

set、std :: mapなどはオプションではありません。

10 Answer


10


順序付き挿入には追加情報は必要ありません。

vectorMyTypes.insert(
    std::upper_bound(vectorMyTypes.begin(), vectorMyTypes.end(), newType),
    newType);

ベクトルが最初からソートされていれば、 upper_bound`は有効な挿入位置を提供します。それで、正しい位置に要素を挿入するだけであれば、完了です。 私はもともと `lower_bound`と言っていましたが、ベクトルが複数の等しい要素を含む場合、 upper_bound`は挿入点を選択します。

これはO(n)個の要素をコピーする必要がありますが、挿入ソートは「非常に速い」と言っていますが、これは速いです。 それが十分に速くない場合は、アイテムをバッチで追加して最後に検証する方法を見つける必要があります。そうしないと、連続した記憶域をあきらめて順序を維持するコンテナに切り替えます

ヒープは、基になるコンテナ内の順序を維持しませんが、最大要素の削除を速くするため、優先度キューなどに適しています。 ベクトルを整然とした順序で維持したいと言っていますが、実際にコレクション全体を反復処理しない場合は、完全に順序付けする必要はないかもしれません。そのため、ヒープが役に立ちます。


6


MeyersのEffective STLのitem 23によると、アプリケーションが3段階でそのデータ構造を使用する場合は、ソート済みベクトルを使用する必要があります。 本から、彼らは以下のとおりです。

_ . セットアップ。 多数の要素を挿入して新しいデータ構造を作成する それに。 この段階では、ほとんどすべての操作が挿入と消去です。 存在しない場合は検索はまれです . 見上げる。 データ構造を参照して、特定の部分を見つけます 情報。 この段階では、ほとんどすべての操作がルックアップです。 挿入と消去はまれであるか存在しません。 多くの検索があります、このフェーズのパフォーマンスは他のフェーズのパフォーマンスを付随的にします。 . *再編成*データ構造の内容を変更します。 おそらく 現在のデータをすべて消去し、その場所に新しいデータを挿入します。 動作上、このフェーズはフェーズ1と同等です。 このフェーズが完了すると、アプリケーションはフェーズ2に戻ります _

データ構造の使用方法がこれに似ている場合は、ソートされたベクトルを使用してから、前述のようにbinary_searchを使用してください。 そうでなければ、典型的な連想コンテナはそれをすべきです、それはそれらの構造がデフォルトで順序付けられているので_set、multi-set、mapまたはmultimap_を意味する


3


新しい要素を挿入する場所を見つけるために二分検索を使用しないのはなぜですか。 その後、必要な位置に正確に挿入します。


1


たくさんの要素をソートされたシーケンスに挿入する必要がある場合は、 `std

merge`を使用してください。潜在的に新しい要素を最初にソートします。

void add( std::vector & oldFoos, const std::vector & newFoos ) {
    std::vector merged;
    // precondition: oldFoos _and newFoos_ are sorted
    merged.reserve( oldFoos.size() + newFoos.size() ); // only for std::vector
    std::merge( oldFoos.begin(), oldFoos.end(),
                newFoos.begin(), newFoos.end(),
                std::back_inserter( merged );
    // apply std::unique, if wanted, here
    merged.erase( std::unique( merged.begin(), merged.end() ), merged.end() );
    oldFoos.swap( merged ); // commit changes
}


0


boost::multi_indexを使用しないのはなぜですか。

NOTE boost::multi_index does not provide memory contiguity, a 要素がメモリの単一ブロックに互いに隣接して格納される `std :: vectors`のプロパティ。


0


あなたがする必要があることがいくつかあります。

  1. 過剰を避けるために `reserve()`を使用することを検討したいかもしれません ベクトル全体の再割り当て。 サイズが大きくなることを知っていれば、(実装にヒューリスティックを使って自動的に行わせるのではなく)自分で `resrve()`を実行することで、ある程度のパフォーマンスを得ることができます。

  2. 挿入位置を見つけるために二分探索を行います。 次に、「リサイズ」と 挿入ポイントに続くすべてを1つずつシフトして、スペースを空けます。

  3. 検討してください:あなたは本当にベクトルを使用したいですか? おそらく set`または map` 優れています。

`lower_bound`に対する二分探索の利点は、挿入ポイントがベクトルの終わりに近い場合、theta(n)の複雑さを払う必要がないことです。


0


二分探索を使用して挿入位置を見つけることは、挿入を行うのにまだO(N)であるため、アルゴリズムをそれほど高速化することにはなりません(ベクトルの先頭に挿入することを検討しますスペースを作成します。

ツリー(別名ヒープ)を挿入するとO(log(N))になり、パフォーマンスが大幅に向上します。

ツリーは、バランスが取れていない限り、挿入に対して最悪の場合のO(N)性能を依然として有することに留意されたい。 AVLツリー


0


  1. 「正しい」位置に要素を挿入したい場合、どうして

    • sort *を使用する予定です。 lower_boundを使って位置を見つけ、ベクトルのinsertメソッドを使って挿入します。 それはまだ新しい項目を挿入するO(N)です。

  2. ヒープはソートされていないため、ヒープは役に立ちません。 それが可能 最小の要素にすばやく到達し、それをすばやく削除して次の最小要素を取得します。 ただし、ヒープ内のデータはソート順に格納されていないため、データを順番に繰り返し処理する必要があるアルゴリズムがある場合は役に立ちません。

    私はあなたが説明を非常に細かくすくい取ることを恐れています、しかしそれはリストがちょうどタスクのための正しい要素ではないように思えます。 `std

    deque`は真ん中に挿入するのに非常に適しています、そして` std :: set`を検討することもできます。 より役立つアドバイスを得るためにデータをソートしておく必要がある理由を説明することをお勧めします。


0


あなたはBTreeやJudy Trieの使用を検討したいかもしれません。

  • 大きなコレクションに連続したメモリを使用したくない場合は、挿入にO(n)時間をかけないでください。

  • 単一の要素に対して少なくとも2項挿入を使用したい場合は、検索境界を小さくできるように複数の要素を事前ソートする必要があります。

  • データ構造にメモリを浪費させたくないので、各データ要素の左右のポインタには関係ありません。


0


他の人が言ったように私はおそらくベクトルを使う代わりにリンクリストからBTreeを作成したと思います。 ソートの問題を乗り越えたとしても、最大サイズが分からないと仮定すると、ベクトルは成長する必要があるときに完全に再割り当てするという問題があります。

リストが異なるメモリページに割り当てられてキャッシュ関連のパフォーマンスの問題を引き起こすことが心配な場合は、ノードを配列に事前に割り当て(オブジェクトをプール)、それらをリストに挿入します。

データ型に値が追加できるのは、それがヒープから割り当てられているのかプールから割り当てられているのかを示します。 こうすれば、プールの空き容量が足りなくなったことを検出した場合、ヒープから割り当てを開始してアサートなどを自分自身にスローして、プールサイズを増やすことができます(またはこれをコマンドラインオプションに設定します)。

これが役に立つことを願っています、私はあなたがすでにたくさんの素晴らしい答えを持っているのを見ます。