C++ Learning

第21回 queue / stack / priority_queue ― コンテナアダプタ

この 3 つは既存のコンテナに「使える操作を絞った薄い皮」を被せたもので、コンテナアダプタと呼ばれます。内部は dequevector を使い、その一部の機能だけを露出します。queue は FIFO(先入れ先出し)、stack は LIFO(後入れ先出し)、priority_queue は常に最大(または最小)を取り出す優先度付きキュー。「これしかやらない」と宣言できることでコードの意図が明快になります。

このページで押さえること
✅ 最低限ここだけ覚える
  • std::queuepush / pop / front(FIFO)
  • std::stackpush / pop / top(LIFO)
  • std::priority_queuepush / pop / top(最大値)
  • アダプタは反復子なし(範囲 for も使えない)
⭐ 余裕があれば読む
  • 内部コンテナ(デフォルト deque / vector)
  • priority_queue でヒープ・最小値にする方法
  • BFS / DFS / Dijkstra での典型的な使い方
  • アダプタを使わず直接 deque を使う選択肢

1. アダプタって何?

これらのアダプタは新しいコンテナを実装しているわけではありません。既存の dequevector を内部に持ち、「使えるメソッドを絞った API」を外に見せているだけ。

アダプタのイメージ
外側: std::queue<int>          ← 使える API: push / pop / front / back / size
        │
        ↓ 内部に抱える
内側: std::deque<int>          ← 本体(機能はぜんぶ持っている)
なぜ API を絞る? 「このデータはキューとして扱う」という意図がコードから読めるようになる。読む人は q.pushq.pop しか出てこないことが保証されるので、変な使われ方を心配する必要がない
反復子がない: アダプタは全要素を走査するインターフェースを持ちませんbegin() / end() / 範囲 for は使えず、front(または top)と pop を繰り返して消費するのが想定された使い方。中身を眺めたいなら deque / vector を直接使います。

2. std::queue ― FIFO

先入れ先出し。最初に入れた要素から順に取り出せます。内部はデフォルトで std::deque

std::queueFIFO
#include <queue> std::queue<int> q; q.push(1); q.push(2); q.push(3); std::cout << q.front(); // 1 (先頭) q.pop(); // 1 を削除 std::cout << q.front(); // 2

よく使うメソッド

使い所: 幅優先探索(BFS)、タスクキュー、ログ処理、生産者/消費者パターン。「古いものから順に処理する」流れはすべて FIFO。

3. std::stack ― LIFO

後入れ先出し。最後に入れた要素から順に取り出せます。内部はデフォルトで std::deque

std::stackLIFO
#include <stack> std::stack<int> st; st.push(1); st.push(2); st.push(3); std::cout << st.top(); // 3 (最後に入れた) st.pop(); // 3 を削除 std::cout << st.top(); // 2

よく使うメソッド

使い所: 深さ優先探索(DFS)、式の評価(逆ポーランド記法)、呼び出し履歴の Undo、括弧の対応チェック。「最後に起きたことから戻る」流れはすべて LIFO。

4. std::priority_queue ― 優先度付きキュー

常に最大値(または最小値)を取り出せる特殊なキュー。内部はデフォルトで std::vector にヒープ構造を構築しています。

std::priority_queue最大ヒープ
#include <queue> std::priority_queue<int> pq; pq.push(3); pq.push(1); pq.push(4); pq.push(1); pq.push(5); // 大きい順に取り出せる while (!pq.empty()) { std::cout << pq.top() << ' '; // 5 4 3 1 1 pq.pop(); }

計算量

最小値を取り出したい場合

デフォルトは最大値。最小値を取り出したいなら std::greater を渡します。

最小ヒープにするstd::greater
#include <queue> #include <functional> // std::greater のため std::priority_queue<int, std::vector<int>, std::greater<int>> minPq; minPq.push(5); minPq.push(1); minPq.push(3); std::cout << minPq.top(); // 1 (最小)
使い所: Dijkstra 法・A* 探索など最短路アルゴリズム、スケジューラ(次のタスクは締切が近いものから)、K 番目に大きい値を保持する、などで主役になります。

5. アダプタを使うべき? 直に deque を使うべき?

FIFO / LIFO は std::deque を直接使っても実現できます(push_back + pop_front で queue、push_back + pop_back で stack)。それでもアダプタを使うメリットは意図の明示

観点アダプタ(queue / stack)直接 deque
意図が読めるFIFO / LIFO と明確何をしているか要確認
使える操作絞られている(誤用を防げる)全部使える(柔軟だが乱用リスク)
反復/全走査不可範囲 for OK
内部コンテナデフォルト deque(変更可)deque そのもの
性能deque と同じ(インライン展開)deque そのもの
指針: 「本当にキュー/スタックとしてだけ扱う」なら std::queue / std::stack途中で中身を覗く必要があるなら std::deque を直接使う。priority_queue は代替が面倒(自前でヒープを管理するのは現実的ではない)なので、ほぼ常にアダプタを使います。

確認クイズ

2 問。

Q1. std::stack<int> st1, 2, 3 を順に push したあと、st.top() が返す値は?

1
2
3
未定義
stack は LIFO(後入れ先出し)。最後に push した 3 が頂上。top() は参照のみで、取り除きたいなら pop()

Q2. std::priority_queue<int> pq3, 1, 4, 1, 5 を push したあと、top() / pop() を繰り返して出力すると?

1 1 3 4 5(小さい順)
3 1 4 1 5(入れた順)
5 4 3 1 1(ユニーク化)
5 4 3 1 1(大きい順)
デフォルトの std::priority_queue最大ヒープ。常に最大値が top にくるので、取り出しは大きい順。重複はそのまま残ります。小さい順にしたければ std::greater を第 3 テンプレート引数で渡します。