web-dev-qa-db-ja.com

何が速いですか:優先キューに挿入するか、遡及的に並べ替えますか?

何が速いですか:優先キューに挿入するか、遡及的に並べ替えますか?

最後に並べ替える必要のあるアイテムをいくつか生成しています。複雑さの点で何が速いのか疑問に思いました。priority_queueまたは同様のデータ構造に直接挿入するか、最後にソートアルゴリズムを使用しますか?

24
static_rtti

nアイテムを優先キューに挿入すると、漸近的な複雑さO(n log n)になるため、複雑さの観点からは、使用するよりも効率的ではありません。 sort一度、最後に。

それが実際により効率的であるかどうかは本当に異なります。テストする必要があります。実際、実際には、挿入を線形配列に(ヒープを構築せずに挿入ソートのように)継続することでさえ、漸近的に悪いであるとしても、最も効率的かもしれません。ランタイム。

21
Konrad Rudolph

あなたの質問に関する限り、これはおそらくゲームの少し遅い時期にあなたに来るでしょうが、完了しましょう。

テストは、特定のコンピューターアーキテクチャ、コンパイラ、および実装についてこの質問に答える最良の方法です。それを超えて、一般化があります。

まず、優先キューは必ずしもO(n log n)ではありません。

整数データが​​ある場合、O(1)時間で機能する優先キューがあります。BeucherとMeyerの1992年の出版物「セグメンテーションへの形態学的アプローチ:流域変換」では、機能する階層キューについて説明しています。範囲が制限された整数値の場合は非常に迅速です。ブラウンの1988年の出版物「カレンダーキュー:シミュレーションイベントセット問題の高速0(1)優先度キューの実装」は、より広い範囲の整数をうまく処理する別のソリューションを提供します。ブラウンの20年後の作業です。パブリケーションは、整数優先キューfastを実行するためのいくつかの素晴らしい結果を生み出しました。しかし、これらのキューの機構は複雑になる可能性があります。バケットソートと基数ソートは依然としてO(1)操作。場合によっては、浮動小数点データを量子化して、O(1)優先度付きキューを利用することもできます。

浮動小数点データの一般的な場合でも、そのO(n log n)は少し誤解を招く可能性があります。 Edelkampの著書「HeuristicSearch:Theory and Applications」には、さまざまな優先度付きキューアルゴリズムの時間計算量を示す次の便利な表があります(優先度付きキューは並べ替えとヒープ管理に相当することを忘れないでください)。

Priority Queue Time Complexities

ご覧のとおり、多くの優先キューには、挿入だけでなく、抽出、さらにはキュー管理にもO(log n)コストがかかります。アルゴリズムの時間計算量を測定するために係数は一般に削除されますが、これらのコストは知っておく価値があります。

しかし、これらすべてのキューには、同等の時間計算量があります。どちらがベストですか? Cris L. Luengo Hendriksによる2010年の論文、「画像分析のための優先キューの再検討」は、この質問に対処しています。

Hold Times for Priority Queues

Hendriksの保留テストでは、優先度キューに[〜#〜] n [〜#〜]の範囲[0、 50]。次に、キューの最上位の要素がデキューされ、[0,2]の範囲のランダムな値でインクリメントされてから、キューに入れられました。この操作を10 ^ 7回繰り返しました。乱数を生成するオーバーヘッドは、測定された時間から差し引かれました。このテストでは、ラダーキューと階層ヒープが非常にうまく機能しました。

キューを初期化して空にする要素ごとの時間も測定されました---これらのテストはあなたの質問に非常に関連しています。

Per-Element Enqueue and Dequeue Times

ご覧のとおり、キューが異なれば、エンキューとデキューに対して非常に異なる応答が返されることがよくあります。これらの数値は、連続操作に優れた優先キューアルゴリズムが存在する可能性がある一方で、優先キュー(実行している操作)を単純に埋めてから空にするためのアルゴリズムの最良の選択がないことを意味します。

あなたの質問を振り返ってみましょう:

何が速いですか:優先キューに挿入するか、遡及的に並べ替えますか?

上に示したように、優先キューは効率的にすることができますが、挿入、削除、および管理には依然としてコストがかかります。ベクトルへの挿入は高速です。償却時間はO(1)であり、管理コストはありません。さらに、ベクトルはO(n)読み取ります。

浮動小数点データがあると仮定すると、ベクトルの並べ替えにはO(n log n)のコストがかかりますが、今回は、優先度付きキューのように複雑さが隠されていません。 (ただし、少し注意する必要があります。クイックソートは一部のデータで非常にうまく動作しますが、最悪の場合の時間計算量はO(n ^ 2)です。一部の実装では、これは重大なセキュリティリスクです。)

並べ替えのコストに関するデータがないのではないかと思いますが、遡及的な並べ替えは、あなたがより良くしようとしていることの本質を捉えているため、より良い選択だと思います。優先キュー管理とポストソートの相対的な複雑さに基づいて、ポストソートはより高速である必要があると思います。しかし、繰り返しますが、これをテストする必要があります。

最後に並べ替える必要のあるアイテムをいくつか生成しています。複雑さの点で何が速いのか疑問に思いました。優先度付きキューまたは同様のデータ構造に直接挿入するか、最後に並べ替えアルゴリズムを使用しますか?

これについては、おそらく上記で説明しました。

しかし、あなたが尋ねなかった別の質問があります。そして、おそらくあなたはすでに答えを知っています。それは安定性の問題です。 C++ STLは、優先キューは「厳密な弱」順序を維持する必要があると述べています。これは、すべての要素が比較可能な「全順序」とは対照的に、同じ優先度の要素は比較不可能であり、任意の順序で配置できることを意味します。 (順序付けのわかりやすい説明があります ここ 。)並べ替えでは、「厳密な弱い」は不安定な並べ替えに類似しており、「全順序」は安定した並べ替えに類似しています。

結果として、同じ優先度の要素をデータ構造にプッシュしたのと同じ順序のままにする必要がある場合は、安定した並べ替えまたは全順序が必要です。 C++ STLの使用を計画している場合、選択肢は1つだけです。優先キューは厳密な弱順序を使用するため、ここでは役に立ちませんが、STLアルゴリズムライブラリの「stable_sort」アルゴリズムがその役割を果たします。

これがお役に立てば幸いです。言及された論文のコピーが必要な場合、または説明が必要な場合はお知らせください。 :-)

83
Richard

データにもよりますが、私は一般的にInsertSortの方が速いと思います。

関連する質問がありましたが、最終的にボトルネックは、遅延ソートを実行していることであり(最終的に必要になったときのみ)、大量のアイテムで、通常、最悪のシナリオがありました。私のQuickSort(すでに順番に)、それで私は挿入ソートを使用しました

キャッシュミスが多い1000-2000要素の並べ替え

だからあなたのデータを分析してください!

5
Soylent Graham

あなたの最初の質問(どちらが速いか)に:それは異なります。テストするだけです。ベクトルで最終結果が必要だとすると、代替案は次のようになります。

#include <iostream>
#include <vector>
#include <queue>
#include <cstdlib>
#include <functional>
#include <algorithm>
#include <iterator>

#ifndef NUM
    #define NUM 10
#endif

int main() {
    std::srand(1038749);
    std::vector<int> res;

    #ifdef USE_VECTOR
        for (int i = 0; i < NUM; ++i) {
            res.Push_back(std::Rand());
        }
        std::sort(res.begin(), res.end(), std::greater<int>());
    #else
        std::priority_queue<int> q;
        for (int i = 0; i < NUM; ++i) {
            q.Push(std::Rand());
        }
        res.resize(q.size());
        for (int i = 0; i < NUM; ++i) {
            res[i] = q.top();
            q.pop();
        }
    #endif
    #if NUM <= 10
        std::copy(res.begin(), res.end(), std::ostream_iterator<int>(std::cout,"\n"));
    #endif
}

$ g++     sortspeed.cpp   -o sortspeed -DNUM=10000000 && time ./sortspeed

real    0m20.719s
user    0m20.561s
sys     0m0.077s

$ g++     sortspeed.cpp   -o sortspeed -DUSE_VECTOR -DNUM=10000000 && time ./sortspeed

real    0m5.828s
user    0m5.733s
sys     0m0.108s

したがって、std::sortstd::priority_queueを打ち負かしますこの場合。しかし、おそらくあなたはより良いまたはより悪いstd:sortを持っており、そしておそらくあなたはヒープのより良いまたはより悪い実装を持っています。または、良くも悪くも、あなたの正確な使用法に多かれ少なかれ適しています。これは、私の発明した使用法とは異なります。「値を含むソートされたベクトルを作成する」。

ランダムデータがstd::sortの最悪のケースにぶつかることはないと確信を持って言えるので、ある意味でこのテストはそれを平坦にするかもしれません。しかし、std::sortの適切な実装では、最悪のケースを構築するのは非常に困難であり、とにかく実際にはそれほど悪くはないかもしれません。

編集:一部の人々がツリーを提案したので、私はマルチセットの使用を追加しました:

    #Elif defined(USE_SET)
        std::multiset<int,std::greater<int> > s;
        for (int i = 0; i < NUM; ++i) {
            s.insert(std::Rand());
        }
        res.resize(s.size());
        int j = 0;
        for (std::multiset<int>::iterator i = s.begin(); i != s.end(); ++i, ++j) {
            res[j] = *i;
        }
    #else

$ g++     sortspeed.cpp   -o sortspeed -DUSE_SET -DNUM=10000000 && time ./sortspeed

real    0m26.656s
user    0m26.530s
sys     0m0.062s

2番目の質問(複雑さ):それらはすべてO(n log n)であり、メモリ割り当てがO(1)かどうか(vector::Push_back and最後に挿入する他の形式は償却されますO(1))「並べ替え」とは比較並べ替えを意味すると仮定します。他の種類の並べ替えは、複雑さが低くなる可能性があります。

5
Steve Jessop

私が理解している限り、あなたのタスクは「多くの挿入を行い、その後すべてをソートする」ように聞こえるので、あなたの問題は優先キューを必要としません。これは、適切なツールではなく、レーザーから鳥を撃つようなものです。そのために標準の並べ替え手法を使用します。

タスクが一連の操作を模倣する場合は、優先度キューが必要になります。各操作は、「セットに要素を追加する」または「セットから最小/最大の要素を削除する」のいずれかです。これは、たとえば、グラフ上で最短経路を見つける問題に使用できます。ここでは、標準の並べ替え手法を使用することはできません。

2
SPIRiT_1984

データを生成している(つまり、リストにまだ含まれていない)ほとんどすべての場合に、挿入の方が効率的だと思います。

優先キューは、挿入するための唯一のオプションではありません。他の回答で述べたように、二分木(または関連するRBツリー)も同様に効率的です。

また、優先度キューがどのように実装されているかを確認します。多くはすでにbツリーに基づいていますが、いくつかの実装は要素の抽出があまり得意ではありません(基本的に、キュー全体を調べて最高の優先度を探します)。

1
Elemental

二分探索木を使ってみませんか?次に、要素は常にソートされ、挿入コストは優先度付きキューと等しくなります。 RedBlackバランスツリーについて読む ここ

1
midtiby

優先キューは通常、ヒープとして実装されます。ヒープを使用した並べ替えは、クイックソートのパフォーマンスが最悪の場合を除いて、平均してクイックソートよりも遅くなります。また、ヒープは比較的重いデータ構造であるため、オーバーヘッドが大きくなります。

最後に並べ替えをお勧めします。

1
user180326

最大挿入優先度キュー操作では、O(lg n)です。

0
John Ortega