ヒープを作成するために使用したいベクトルがあります。C++ の make_heap 関数を使用するべきか、ベクターを優先キューに入れるべきかわかりません。性能的にはどちらが優れていますか?いつどちらを使用する必要がありますか?
6 に答える
性能面での違いはありません。std::priority_queue
コンテナーとまったく同じヒープ関連の関数呼び出しをクラスにラップする単なるアダプター クラスです。の仕様はstd::priority_queue
公然と述べています。
公開されたヒープ関連の関数から を構築して直接呼び出すと、外部アクセスの可能性にheap
さらされたままになり、ヒープ/キューの整合性が損なわれる可能性があります。そのアクセスを「標準的な」最小値に制限するバリアとして機能します:std::vector
std::priority_queue
push()
pop()
top()
また、キュー インターフェイスを「標準的な」一連の操作に適合させることで、同じ外部仕様に準拠する優先度キューの他のクラスベースの実装と統一し、交換可能にします。
priority_queue は (少なくとも通常は) ヒープとして実装されます。そのため、本当の問題は、priority_queue が必要なものを提供するかどうかです。make_heap を使用すると、すべての要素にアクセスできます。priority_queue を使用すると、要素へのアクセスが非常に制限された少数の操作しかありません (基本的には、アイテムを挿入し、キューの先頭にあるアイテムを削除するだけです)。
C++11 標準
C++11 N3337 標準ドラフトは、「23.6.4.1 priority_queue コンストラクター」std::make_heap
のコンストラクターで使用されることを指定します。std::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
メソッドの実際の呼び出しは保証されておらず、機能的な動作のみが保証されていると思います。
これはすべて、 https://stackoverflow.com/a/11266558/895245std::priority_queue
がラッパーであることについて言及したことを裏付けていますstd::make_heap
。
g++
6.4 stdlibc++ ソースにステップ デバッグして、priority_queue
転送先を確認します。make_heap
Ubuntu の 16.04 デフォルトg++-6
パッケージまたはソースからの GCC 6.4 ビルドでは、追加のセットアップなしで C++ ライブラリにステップインできます。
これを使用すると、 が基になる を持つファミリstd::priority_queue
の単なるラッパーであることを簡単に確認できます。これは、パフォーマンスが同じであることを意味します。std::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_queue
、 にstd::vector
.
ここでfinish
、GDB で実行してキュー コンストラクターを見つけ、再びステップ インします。これにより、実際のキュー コンストラクターが表示されます/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); }
これは明らかに、オブジェクトstd::make_heap
の上に転送するだけです。c
でソース ファイルを開き、次vim
の定義を見つけますc
。
template<typename _Tp, typename _Sequence = vector<_Tp>,
typename _Compare = less<typename _Sequence::value_type> >
class priority_queue
{
[...]
_Sequence c;
c
したがって、それは であると結論付けvector
ます。
他のメソッドに足を踏み入れるか、ソースをさらに調べると、他のすべての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))
) と見なすことができますが、残りの半分はそのままになります。
priority_queue
それを呼び出すとどうなるかpush
: 新しい要素を基になるコンテナーの後ろにプッシュしpush_heap
、ヒープ プロパティを優先して保持するように呼び出します (最初の要素が最大であることが重要です)。
パフォーマンスの問題よりも、ソリューションの設計と特定の要件を検討したほうがよいと思います。
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