コンテナアダプタ
コンテナを利用したクラス
コンテナアダプタは、内部にSTLコンテナを持つクラスです。
クラス名 | 説明 | ヘッダファイル |
---|---|---|
stack | スタック構造 後入れ先出し LIFO (Last In First Out) |
<stack> |
queue | キュー構造 先入れ先出し FIFO (First In First Out) |
<queue> |
priority_queue | 優先順位付きキュー 要素に優先順位をつけ、その順位が高い順に取り出す |
<queue> |
コンテナアダプタは要素の順序が重要なクラスで、特定の条件に従ってデータが出し入れされます。
stack
stackクラスは、データを入れた順とは逆から取り出されます。
(後に入れたものから先に出される、後入れ先出し)
「1、2、3」の順に入力した場合、「3、2、1」の順に取り出されます。
「stack」は「積み上げる」という意味で、データを上に重ねていくイメージです。
積み上げたデータから値を取り出すには、上から順に取り出す必要があります。
queue
queueクラスは、データを入れた順に取り出されます。
(先に入れたものから先に出される、先入れ先出し)
「1、2、3」の順に入力した場合、「1、2、3」の順に取り出されます。
「queue」は「列」という意味で、順番待ちの列のイメージです。
データは列の後ろに並べていき、取り出しは列の先頭から行います。
priority_queueクラス
priority_queueクラスは、あらかじめ指定した優先順位に従ってデータが取り出されます。
デフォルトでは降順です。
priority_queueクラスについて
自作クラスを格納する
priority_queueクラスでユーザー定義クラスを使用するには<
演算子をオーバーロードする必要があります。
class C
{
int x;
int y;
public:
C(int a, int b) : x(a), y(b) {}
//xが同値ならyで比較する
bool operator <(const C& r) const
{
if (this->x < r.x) {
return true;
}
else if (this->x > r.x) {
return false;
}
return (this->y < r.y);
}
};
これはデフォルトの比較関数として<
演算子による比較を行うstd::less
が使用されているためです。
比較関数を変更した場合はそれに対応する演算子のオーバーロードが必要になります。
優先順位の変更
priority_queueクラスはデフォルトでは降順で要素が取り出されます。
優先順位の変更はテンプレート仮引数で比較関数を指定します。
//std::greaterで比較
std::priority_queue<
int,
std::vector<int>,
std::greater<int>> q;
q.push(3);
q.push(1);
q.push(7);
while (!q.empty())
{
std::cout << q.top() << ", ";
q.pop();
}
1, 3, 7
テンプレート仮引数の第三引数が比較関数の指定です。
上記の例では昇順で比較するstd::greater
を使用しています。
これは>
演算子による比較を行うので、自作クラスを使用する場合は>
演算子のオーバーロードが必要です。
上記コードのテンプレート仮引数の第二引数にはvectorクラスを指定しています。
これはpriority_queueクラスが内部で使用するコンテナクラスで、デフォルトではvectorクラスが使用されます。
ここに指定できるクラスはランダムアクセスイテレータを持ち、front
メンバ関数、push_back
メンバ関数、pop_back
メンバ関数、emplace_back
メンバ関数を持つクラスです。
C++標準ではvectorクラスとdequeクラスが該当します。
上記の要件を満たすユーザー定義のクラスを指定することも可能です。
優先順位を任意に指定する例です。
#include <iostream>
#include <queue>
#include <functional>
struct S
{
int x;
int y;
//大小比較関数(降順)
static bool comparer(S l, S r)
{
if (l.x < r.x) {
return true;
}
else if (l.x > r.x) {
return false;
}
return (l.y < r.y);
}
};
int main()
{
{
//静的メンバ関数をstd::functionに格納
std::function<bool(S, S)> comparer = S::comparer;
//autoでも可
//auto comparer = S::comparer;
std::priority_queue<
S,
std::vector<S>,
decltype(comparer)> q(comparer); //コンストラクタに関数オブジェクトを指定する
q.push({ 1, 5 });
q.push({ 3, 4 });
q.push({ 1, 3 });
while (!q.empty())
{
const S& t = q.top();
std::cout << t.x << ", " << t.y << std::endl;
q.pop();
}
}
std::cout << std::endl;
{
//大小比較関数(昇順)をラムダ式で定義
auto comparer = [](S l, S r) {
if (l.x > r.x) {
return true;
}
else if (l.x < r.x) {
return false;
}
return (l.y > r.y);
};
std::priority_queue<
S,
std::vector<S>,
decltype(comparer)> q(comparer); //コンストラクタに関数オブジェクトを指定する
q.push({ 1, 5 });
q.push({ 3, 4 });
q.push({ 1, 3 });
while (!q.empty())
{
const S& t = q.top();
std::cout << t.x << ", " << t.y << std::endl;
q.pop();
}
}
}
3, 4 1, 5 1, 3 1, 3 1, 5 3, 4
テンプレート仮引数の第三引数はデータ型の指定なので、decltype
を使用して関数オブジェクトのデータ型を指定します。
実際に呼び出す関数はpriority_queueのコンストラクタに指定します。
メンバ関数一覧
コンテナアダプタで共通するメンバ関数の対応一覧です。
stack | queue | priority_queue | 説明 | |
---|---|---|---|---|
要素アクセス | ||||
top | ○ | × | ○ | トップ要素へのアクセス |
front | × | ○ | × | 先頭要素へのアクセス |
back | × | ○ | × | 末尾要素へのアクセス |
容量 | ||||
empty | ○ | ○ | ○ | オブジェクトが空か否かの判定 |
size | ○ | ○ | ○ | 要素数の取得 |
コンテナの変更 | ||||
pop | ○ | ○ | ○ | 要素の削除 |
push | ○ | ○ | ○ | 要素の追加 |
emplace | ○ | ○ | ○ | 要素を構築して追加 |
swap | ○ | ○ | ○ | オブジェクトの入れ替え |
stack | queue | priority_queue | 説明 |
vectorクラスと共通の関数は当該ページを参照してください。
以下は、上記のページでは説明していない関数について説明します。
以下のサンプルコードでは戻り値のデータ型を明示的に記述しているものもありますが、やたらと長いデータ型名が多いのでauto(型推論)を使用することをお勧めします。
decltypeはコンテナ型やデータ型に依存しない記述ができるので、auto
が使用できない場面ではこちらが便利です。
std::vector<int> vec;
//全て同じデータ型
std::vector<int>::iterator it1;
auto it2 = vec.begin();
decltype(vec)::iterator it3;
top関数
top
関数は、トップ要素にアクセスします。
#include <iostream>
#include <stack>
#include <queue>
int main()
{
std::stack<int> stack;
stack.push(3);
stack.push(2);
stack.push(1);
std::cout << stack.top() << std::endl;
std::priority_queue<int> pqueue;
pqueue.push(3);
pqueue.push(2);
pqueue.push(1);
std::cout << pqueue.top() << std::endl;
std::cin.get();
}
1 3
トップ要素は、stackクラスの場合は最後に入れたデータです。
priority_queueクラスの場合は最も優先順位の高い要素です。
デフォルトでは降順に並べられるので、要素中の最も大きい値です。
要素アクセスなので値の取り出しだけではなく代入もできます。
(要素への参照が返される)
std::stack<int> stack;
stack.push(3);
stack.push(2);
stack.push(1);
stack.top() = 10;
std::cout << stack.top() << std::endl;
10
なお、この関数は単純な要素アクセスであって要素の取り出しではありません。
つまり要素を削除する機能はありません。
front関数
back関数
front
関数は、先頭要素にアクセスします。
back
関数は、末尾要素にアクセスします。
std::queue<int> queue;
queue.push(3);
queue.push(2);
queue.push(1);
std::cout << queue.front() << std::endl;
std::cout << queue.back() << std::endl;
3 1
queueクラスは先入れ先出しなので、先頭要素は最初に入れたデータです。
末尾要素は最後に入れたデータです。
この関数も要素を削除する機能はありません。
pop関数
push関数
pop
関数は、トップ要素を削除します。
push
関数は、要素を追加します。
#include <iostream>
#include <stack>
#include <queue>
int main()
{
std::stack<int> stack;
stack.push(3);
stack.push(2);
stack.push(1);
while (!stack.empty()) {
std::cout << stack.top() << " ";
stack.pop();
}
//1 2 3
std::cout << std::endl;
std::queue<int> queue;
queue.push(3);
queue.push(2);
queue.push(1);
while (!queue.empty()) {
std::cout << queue.front() << " ";
queue.pop();
}
//3 2 1
std::cout << std::endl;
std::priority_queue<int> pqueue;
pqueue.push(3);
pqueue.push(2);
pqueue.push(1);
pqueue.push(4);
while (!pqueue.empty()) {
std::cout << pqueue.top() << " ";
pqueue.pop();
}
//4 3 2 1
std::cout << std::endl;
std::cin.get();
}
これらの関数の戻り値はありません。
pop
関数はtop関数で得られる要素を削除します。
コンテナアダプタは、要素を追加/削除する位置は固定なので「位置を指定して挿入/削除」といった機能は提供されていません。