記事ディレクトリ
1、優先キュー
1.1 priority_queue の導入と使用
翻訳:
1. プライオリティ キューは、厳密に弱い順序付け基準に基づいたコンテナ アダプタです。 最初の要素は常に、それに含まれる要素の中で最大になります。
2. このコンテキストは ヒープ に似ており、 は任意の場所に挿入できます。ヒープ要素内で最大のヒープ要素 (優先キューの先頭にある要素) のみを取得できます。
3. プライオリティ キューはコンテナ アダプタとして実装されます。コンテナ アダプタは、特定のコンテナ クラスをその基礎となるコンテナ クラスとしてカプセル化します。キューは、その要素にアクセスするための特定のメンバ関数のセットを提供します。要素は、優先キューの先頭と呼ばれる特定のコンテナの「末尾」からポップされます。
4. 基礎となるコンテナには、任意の標準コンテナ クラス テンプレートを使用することも、他の特別に設計されたコンテナ クラスを使用することもできます。 コンテナはランダム アクセス イテレータを介してアクセス可能であり、次の操作をサポートする必要があります:
empty(): コンテナが空かどうかを検出します
size(): コンテナ内の有効な要素の数を返します
front(): コンテナ内の最初の要素への参照を返します
push_back( ): コンテナの最後に要素を挿入
Pop_back(): コンテナの最後にある要素を削除
5. 標準コンテナ クラスの Vector と deque は、次の要件を満たしています a> 6. ヒープ構造が常に内部的に維持されるように、ランダム アクセス イテレータをサポートする必要があります。コンテナー アダプターは、必要に応じてアルゴリズム関数 make_heap、push_heap、pop_heap を自動的に呼び出すことで、これを自動的に行います。 ベクターが使用されます。 デフォルトでは、特定の priority_queue クラスのインスタンス化にコンテナ クラスが指定されていない場合、。
1.2 priority_queue の使用
プライオリティ キューは、デフォルトで基盤となるデータ ストレージ コンテナとしてベクターを使用し、ベクターのヒープ アルゴリズムを使用してベクター内の要素をヒープ構造に構築します。そのため、 priority_queueはヒープです太字、 ヒープが必要な場合はどこでも priority_queue の使用を検討できます。注: デフォルトでは、priority_queue は大きな山です。
関数宣言 | インターフェースの説明 |
---|---|
priority_queue()/priority_queue(最初,最後) | 空の優先キューを構築する |
空の( ) | 優先キューが空かどうかを確認し、空の場合は true を返し、そうでない場合は false を返します。 |
上( ) | 優先キュー内の最大 (最小の要素)、つまりヒープの最上位の要素を返します。 |
押す() | 要素 x を優先キューに挿入します |
ポップ() | 優先キュー内の最大 (最小) 要素、つまりヒープの最上位要素を削除します。 |
注:
1. デフォルトでは、priority_queue は大きなパイルです。
#include <vector>
#include <queue>
#include <functional> // greater算法的头文件
int main()
{
// 默认情况下,创建的是大堆,其底层按照小于号比较
vector<int> v{
3,2,7,6,0,4,1,9,8,5 };
priority_queue<int> q1;
for (auto& e : v) q1.push(e);
while (!q1.empty())
{
cout << q1.top() << " ";
q1.pop();
}
cout << endl;
// 如果要创建小堆,将第三个模板参数换成greater比较方式
priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());
while (!q2.empty())
{
cout << q2.top() << " ";
q2.pop();
}
cout << endl;
return 0;
}
2. カスタム タイプのデータを priority_queue に配置する場合、ユーザーはカスタム タイプで > または < のオーバーロードを指定する必要があります。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{
}
bool operator<(const Date& d)const
{
return (_year < d._year) ||
(_year == d._year && _month < d._month) ||
(_year == d._year && _month == d._month && _day < d._day);
}
bool operator>(const Date& d)const
{
return (_year > d._year) ||
(_year == d._year && _month > d._month) ||
(_year == d._year && _month == d._month && _day > d._day);
}
friend ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
private:
int _year;
int _month;
int _day;
};
void TestPriorityQueue()
{
// 大堆,需要用户在自定义类型中提供<的重载
priority_queue<Date> q1;
q1.push(Date(2018, 10, 29));
q1.push(Date(2018, 10, 28));
q1.push(Date(2018, 10, 30));
cout << q1.top() << endl;
// 如果要创建小堆,需要用户提供>的重载
priority_queue<Date, vector<Date>, greater<Date>> q2;
q2.push(Date(2018, 10, 29));
q2.push(Date(2018, 10, 28));
q2.push(Date(2018, 10, 30));
cout << q2.top() << endl;
}
int main()
{
TestPriorityQueue();
return 0;
}
シミュレーションの実装:
priority_queue はヒープであり、デフォルトは大きなヒープであることを学習しました。基礎となるコンテナはベクターをカプセル化するため、priority_queue のシミュレーションと実装は比較的簡単です。
#include<vector>
#include<functional>
using namespace std;
namespace lcx
{
// 仿函数
template <class T>
class Less
{
public:
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
template <class T>
class Greater
{
public:
bool operator()(const T& x, const T& y)
{
return x > y;
}
};
template <class T, class Container = vector<T>, class Compare = Greater<T>>
class priority_queue
{
public:
priority_queue()
{
}
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last)
:_con(first, last)
{
for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
{
adjust_down(i);
}
}
bool empty() const
{
return _con.empty();
}
size_t size() const
{
return _con.size();
}
const T& top() const
{
return _con[0];
}
void push(const T& x)
{
_con.push_back(x);
adjust_up(_con.size() - 1);
}
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
adjust_down(0);
}
private:
void adjust_up(int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
//if (_con[child] > _con[parent])
if(comp(_con[child], _con[parent]))
{
swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else break;
}
}
void adjust_down(size_t parent)
{
size_t child = parent * 2 + 1;
while (child < _con.size())
{
//if (child + 1 < _con.size()
// && _con[child + 1] > _con[child])
if(child + 1 < _con.size()
&& comp(_con[child + 1], _con[child]))
{
child++;
}
//if (_con[child] > _con[parent])
if (comp(_con[child], _con[parent]))
{
swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else break;
}
}
private:
Container _con;
Compare comp;
};
};
ヒープに詳しくない学生がいる場合は、ヒープについて特に説明した私の別の記事を読むことができます:C 言語によるヒープ実装の詳細バージョン
2. コンテナアダプター
2.1 アダプターとは何ですか
アダプターは設計パターンです(設計パターンは、繰り返し使用され、ほとんどの人に知られ、分類され、カタログ化されたコード設計エクスペリエンスのセットです)、 a>このモードは、クラスのインターフェイスを顧客が望む別のインターフェイスに変換することです。
2.2 STL標準ライブラリのスタックとキューの基礎構造
要素はスタックやキューに格納することもできますが、STL ではコンテナに分類されません。代わりに、コンテナ アダプタ、 これは、スタックとキューが他のコンテナのインターフェイスをラップしているだけであるためです。STL では、スタックとキューはデフォルトで次のように deque を使用します。
3
3.1 デキューの原理の概要
deque (両端キュー): 両開きの「連続」空間データ構造です。両開きの意味は次のとおりです。< a i=2>両端の挿入と削除で、計算量は O(1) ベクトルに比べて先頭の挿入効率が高く、要素の移動が不要; リストに比べて、スペース使用率が比較的高くなります。
deque は真に連続した空間ではありませんが、連続した小さな空間で構成されています。実際の deque は動的な 2 次元配列とその基礎となる構造に似ています。以下の図:
両端キューの最下層は架空の連続空間であり、実際にはセグメント化されて連続しています。その「全体的な連続性」とランダムの錯覚を維持するため、アクセスする場合、それはイテレータの両端キュー に該当するため、以下の図に示すように、両端キューのイテレータ設計はより複雑になります。
両端キューはどのように維持しますか反復子の助けを借りて想像上の連続性を実現するのですか?構造はどうですか?
3.2 deque の欠陥
vector と比較した場合、deque の利点は次のとおりです。head の挿入および削除の際に要素を移動する必要がなく、非常に効率的です。また、展開する際には要素を移動する必要がなく、移動する必要がなく、要素数が多いため、効率が高くなければなりません。
リストと比較すると、 最下層は連続空間であり、 空間使用率は比較的高く、 追加のフィールドを保存する必要はありません。
deque には致命的な欠陥があります。 はトラバーサルには適していません。これは、トラバーサル中に、deque の反復子が狭い領域に移動したかどうかを頻繁に検出する必要があるためです。 . 境界線が存在し、非効率につながります。
逐次的なシナリオでは、頻繁な走査が必要になる場合があるため、実際には、線形構造が必要な場合、ほとんどの場合、ベクトルとリストが優先されます。 a> a> STL はこれをスタックとして使用し、基礎となるデータ構造をキューに入れます。 現在確認できるアプリケーションの 1 つは、 、deque のアプリケーションはそれほど多くありません。
4. スタックとキューの基礎となるデフォルトのコンテナとして deque を選択するのはなぜですか?
スタック は特殊な後入れ先出しの線形データ構造です。したがって、push_back() および Pop_back() オペレーションを備えた任意の線形構造を基礎となるコンテナとして使用できます。たとえば、ベクトルとリストの両方を使用できます。
キュー は、先入れ先出しの特殊な線形データ構造です。 Push_back および Pop_front 操作を備えた構造体はキューとして使用でき、基になるコンテナー (リストなど)。ただし、STL では、デフォルトでスタックとキューの基礎となるコンテナとして deque が選択されます。主な理由は次のとおりです。
1. スタックとキュー は必要ありません。 traversed< a i=6> (したがって、スタックとキューにはイテレータがありません)、一方または両方の固定端でのみ操作する必要があります。 2. スタック内の要素が増加すると、deque は Vector より効率的です (拡張時に大量のデータを移動する必要がありません)。キュー内の要素が増加すると、deque が実行されます。効率が高いだけでなく、メモリ使用量も向上します。 は、deque の利点を組み合わせ、その欠点を完全に回避します。