web-dev-qa-db-ja.com

make_heapとPriorityQueueはいつ使用する必要がありますか?

ヒープを作成するために使用したいベクトルがあります。 C++ make_heap関数を使用する必要があるのか​​、ベクターを優先キューに入れる必要があるのか​​わかりませんか?パフォーマンスの点でどちらが優れていますか?どちらを使用する必要がありますか?

30
rolloff

パフォーマンスのサームに違いはありません。 _std::priority_queue_は、コンテナとまったく同じヒープ関連の関数呼び出しをクラスにラップする単なるアダプタクラスです。 _std::priority_queue_の仕様は、それを公然と述べています。

公開された_std::vector_からヒープベースの優先度キューを構築することにより(ヒープ関連の関数を直接呼び出すことにより)、外部アクセスの可能性に対してオープンに保ち、ヒープ/キューの整合性を損なう可能性があります。 _std::priority_queue_は、「正規の」最小値へのアクセスを制限するバリアとして機能します:Push()pop()top()など。 -規律施行措置。

また、キューインターフェイスを「標準的な」一連の操作に適合させることで、同じ外部仕様に準拠する優先キューの他のクラスベースの実装と統一して互換性を持たせることができます。

30
AnT

Priority_queueは(少なくとも通常は)ヒープとして実装されます。そのため、本当の問題は、priority_queueが必要なものを提供するかどうかです。 make_heapを使用する場合でも、すべての要素にアクセスできます。 priority_queueを使用する場合、要素へのアクセスを非常に制限する操作はわずかです(基本的には、アイテムを挿入し、キューの先頭にあるアイテムを削除するだけです)。

4
Jerry Coffin

C++ 11標準

C++ 11 N3337標準ドラフト 「23.6.4.1priority_queueコンストラクター」のstd::make_heapのコンストラクターでstd::priority_queueが使用されることを指定します。

明示的なpriority_queue

2効果:compをxで初期化し、cをyで初期化します(必要に応じて、コピー構築または移動構築)。 make_heap(c.begin()、c.end()、comp)を呼び出します。

そして他の方法は言う:

void Push(const value_type&x);

効果:c.Push_back(x); Push_heap(c.begin()、c.end()、comp)

ただし、新しいn4724では、コンストラクター以外のメソッドの表現が「あたかも」になるため、*_heapメソッドの実際の呼び出しは保証されず、機能的な動作のみが保証されると思います。

デバッグをg++ 6.4 stdlibc ++ソースにステップインして、priority_queuemake_heapに転送されることを確認します。

Ubuntuの16.04のデフォルトのg++-6パッケージまたは ソースからのGCC 6.4ビルド では、追加のセットアップなしでC++ライブラリにステップインできます。

これを使用すると、std::priority_queuestd::make_heapファミリの単なるラッパーであり、基礎となるstd::vectorがあることを簡単に確認できます。これは、パフォーマンスが同じであることを意味します。

a.cpp:

#include <cassert>
#include <queue>

int main() {
    std::priority_queue<int> q;
    q.emplace(2);
    q.emplace(1);
    q.emplace(3);
    assert(q.top() == 3);
    q.pop();
    assert(q.top() == 2);
    q.pop();
    assert(q.top() == 1);
    q.pop();
}

コンパイルとデバッグ:

g++ -g -std=c++11 -O0 -o a.out ./a.cpp
gdb -ex 'start' -q --args a.out

ここで、コンストラクターstd::priority_queue<int> qに最初にステップインするとvectorコンストラクターに入るので、std::priority_queuestd::vectorが含まれていることはすでに推測できます。

ここで、GDBでfinishを実行してキューコンストラクターを見つけ、もう一度ステップインすると、実際のキューコンストラクター/usr/include/c++/6/bits/stl_queue.hに移動します。

443       explicit
444       priority_queue(const _Compare& __x = _Compare(),
445              _Sequence&& __s = _Sequence())
446       : c(std::move(__s)), comp(__x)
447       { std::make_heap(c.begin(), c.end(), comp); }

これは明らかに、cオブジェクトの上にあるstd::make_heapに転送するだけです。

したがって、ソースファイルをvimで開き、cの定義を見つけます。

  template<typename _Tp, typename _Sequence = vector<_Tp>,
       typename _Compare  = less<typename _Sequence::value_type> >
    class priority_queue
    {

      [...]

      _Sequence  c;

したがって、cvectorであると結論付けます。

他のメソッドにステップインするか、ソースをさらに調べると、他のすべてのpriority_queueメソッドもstd::make_heap関数ファミリーに転送されることが簡単にわかります

ヒープの平均挿入時間はヒープの方が短いため、ヒープと言う、バランスの取れたBSTの選択は理にかなっています。以下を参照してください。 ヒープと二分探索木(BST)

_priority_queue_はコンテナではありません。これは、特定の基盤となるコンテナを使用するコンテナアダプタです。 vectorまたはdequeであり、データを操作するための特定のメソッドセットを提供します。さらに、その実装は_*_heap_アルゴリズムに依存しています。

たとえば、新しい値をvectorにプッシュするときはいつでも、_Push_heap_を呼び出して、ヒープと見なされる範囲を拡張する必要があります。 _priority_queue_を使用しない場合、たとえば、vectorの半分をヒープ(std::make_heap(v.begin(), v.begin() + (v.size() / 2)))と見なすことができますが、残りの半分は次のようになります-です。

Pushを呼び出すと_priority_queue_が行うこと:新しい要素を基になるコンテナの後ろにプッシュし、_Push_heap_を呼び出して、ヒーププロパティの優先順位を維持します(最初の要素のみが重要です)最高になるために)。

パフォーマンスの問題よりも、ソリューションの設計と特定の要件を検討したほうがよいと思います。

1
Oleg

make_heapは、例として、ヒープを出力するなど、カプセル化を犠牲にして柔軟性を実現します。

Make_heapの興味深い使用法は、マージの片側でmake_heapを使用して、n/2(log(n/2))の最悪の場合のインプレースマージを実現するインプレースマージソートです。

この例は、入力ベクトルの使用と、作成されたヒープの出力を示しています。

#include <queue>
#include <iostream>
#include <string>
#include <vector>
using namespace std;

void print(string prefix,vector<int>& v)
{
  cout << prefix;
  for(int i : v)
     cout << i << " ";
  cout << endl;
}

int main()
{
  vector<int> v={1,2,9,0,3,8,4,7,1,2,9,0,3,8,4,7};
  typedef priority_queue< int,vector<int>,greater<int> > MinQ;
  MinQ minQ(v.begin(),v.end()); //minQ
  print("After priority_queue constructor: ",v);

  make_heap(v.begin(),v.end(),greater<int>());
  print("After make_heap: ", v);
  return 0;
}

出力:

After priority_queue constructor: 1 2 9 0 3 8 4 7 1 2 9 0 3 8 4 7
After make_heap: 0 1 0 1 2 3 4 7 2 3 9 8 9 8 4 7
0
edW

そのベクトルを変更したくない場合は、別のベクトルを作成するため、priority queueを使用する必要があります。ただし、編集する余裕がある場合は、make_heapを使用すると、補助スペースが作成されず、そのベクトルがインプレースで変更されないため、スペースが節約されるため、明らかに優れています。さらに、優先キューは簡単に実装できます。たとえば、要素のポップ中にmake_heapを使用する場合、最初にpop_heap、次にpop_back ..の2つのコマンドを使用する必要がありますが、優先度付きキューの場合は1つのコマンドで実行できます。同様に、要素をヒープにプッシュしている間。

これで、優先キューはコンテナではなく、make_heap操作で使用されるのと同じヒープ操作を使用するベクトルまたはdequeとして基礎となるコンテナを使用するため、両方のパフォーマンスは同じになります。したがって、要件に応じて1つを使用する必要があります。

0
shiwang