std::set は重複のない値の集合で、要素は自動でソートされます。「すでに見た ID か?」「ユニークな要素を列挙」などの存在確認が主な用途。さらに、順序は要らず速さだけ欲しいなら、ハッシュ版の std::unordered_set が平均 O(1) で動きます。本章では 2 つの set の使い分けと、集合演算(set_intersection など)の基本を整理します。
s.insert(x):重複は自動で無視されるs.count(x) > 0 または s.contains(x)(C++20)std::set:ソート済み・O(log n)std::unordered_set:順序なし・O(1) 平均set_intersection など)std::multiset(重複を許す版)vector に詰めて sort + unique のほうが速い場面下のフィールドに数を入れて insert を何度か押してみてください。同じ値を複数回入れても重複しないこと、並びが自動でソートされることが見えます。
Q. set は map と何が違う?
→ map はキーと値のペア、set は値だけ。構造的には map の「キーだけ版」と考えて OK。用途も「重複なしの値の集まり」「ある値が存在するか調べる」といった集合的なものに特化。
Q. set なのにソートされるのはなぜ?
→ std::set は内部が平衡二分木(赤黒木)で実装されているため、並びが自然とソート順になります。これが tree 版の set の特徴。順序が不要ならハッシュ版(unordered)のほうが速い。
Q. 重複を許して個数を数えたい場合は?
→ std::multiset(同じ値を何個でも保持)か、std::map<T, int>(値 → 個数)のカウンタ方式が一般的。
重複を許さない値の集合。要素の存在確認が主な用途です。
s.insert(x) ― 追加(既存なら何もしない)s.erase(x) ― 削除s.count(x) ― 0 or 1。存在確認の定番s.contains(x) ― C++20 から。意味がより明快s.size() / s.empty() ― 他コンテナと共通s.find(x) ― 見つかれば反復子、なければ s.end()operator< を定義するか、比較関数を std::set<T, Compare> の第 2 引数で渡します。第 28 回 自作クラスを std::sort で並べる で扱った operator< がそのまま使えます。
同じ「重複なしの集合」でも、内部がハッシュテーブルの版があります。順序を捨てる代わりに検索/挿入/削除が平均 O(1)。
| 観点 | std::set(tree) | std::unordered_set(hash) |
|---|---|---|
| 計算量 | O(log n) | O(1) 平均(最悪 O(n)) |
| 順序 | 昇順で列挙できる | 順序なし |
| 「最小値」取り出し | *s.begin() で可能 | 全走査が必要 |
| 自作キー型 | operator< が必要 | std::hash の特殊化が必要 |
| 選ぶ基準 | 順序が要る/範囲クエリ | 速さ最優先/順序不要 |
unordered_* は平均 O(1) ですが、ハッシュが偏ると最悪 O(n) まで落ちます。キー分布が怪しい場面(ユーザー入力など)では set のほうが安定。ライブラリ標準の std::hash<int> や std::hash<std::string> は通常問題になりません。
set_intersection など)set の特徴を活かした集合演算も標準ライブラリ(<algorithm>)にあります。
他にも:
std::set_union ― 和集合std::set_difference ― 差集合std::set_symmetric_difference ― 対称差これらは「ソート済みの 2 つの区間」を前提としており、std::set に加え、std::sort 済みの std::vector にも適用できます。詳しくは STEP 9 の「集合演算アルゴリズム」で扱います。
unordered_setsetset + set_intersection などvector に詰めて std::sort + std::unique + erase のほうがキャッシュ効率が良く、実測で set より速いことがよくあります。
3 問。
std::set<int> s; s.insert(3); s.insert(3); s.insert(3); の後、s.size() は?std::multiset を使うか、std::vector に詰めます。std::unordered_set<std::string> を選ぶ適切なケースは?Student を std::set<Student> に入れるために必要なのは?Student に operator== を定義するStudent に operator< を定義する(または比較関数を第 2 引数で渡す)Student に hash 関数を定義するstd::set は内部が二分木で、要素の大小比較で並べて保持します。operator< が必要。operator== は不要(!(a<b) && !(b<a) で等価を判定)。hash は unordered_set を使う場合に必要。