C++のstd::set
は、重複しない要素を自動的にソートして管理するための便利なデータ構造です。
この記事では、std::set
の基本概念から特徴、基本操作、応用操作、パフォーマンスの考慮点、そして実践例までをわかりやすく解説します。
初心者の方でも理解しやすいように、具体的なサンプルコードとその実行結果を交えながら説明していきますので、ぜひ最後までご覧ください。
std::setとは
std::setの基本概念
std::set
はC++標準ライブラリに含まれるコンテナの一つで、集合(set)を表現します。
集合とは、数学的な概念で、重複しない要素の集まりを指します。
std::set
はこの概念をプログラム上で実現するためのデータ構造です。
std::setの特徴
重複要素の排除
std::set
の最大の特徴は、同じ値の要素を複数持つことができない点です。
つまり、重複する要素が自動的に排除されます。
これにより、ユニークな要素だけを保持することができます。
自動ソート
std::set
に要素を追加すると、その要素は自動的にソートされます。
デフォルトでは、要素は昇順にソートされますが、カスタムコンパレータを使用することで、ソート順を変更することも可能です。
高速な検索
std::set
は内部的にバランスの取れた二分探索木(通常は赤黒木)を使用しているため、要素の検索、挿入、削除が効率的に行えます。
これにより、平均的な操作の時間計算量はO(log n)となります。
std::setの基本操作
std::setの宣言と初期化
デフォルトコンストラクタ
std::set
を宣言する際には、デフォルトコンストラクタを使用して空の集合を作成することができます。
#include <set>
#include <iostream>
int main() {
std::set<int> mySet;
return 0;
}
イニシャライザリストを使った初期化
イニシャライザリストを使用して、std::set
を初期化することも可能です。
#include <set>
#include <iostream>
int main() {
std::set<int> mySet = {1, 2, 3, 4, 5};
return 0;
}
要素の追加
insertメソッド
insertメソッド
を使用して、std::set
に要素を追加します。
重複する要素は追加されません。
#include <set>
#include <iostream>
int main() {
std::set<int> mySet;
mySet.insert(1);
mySet.insert(2);
mySet.insert(2); // 重複する要素は追加されない
for (int elem : mySet) {
std::cout << elem << " ";
}
return 0;
}
emplaceメソッド
emplaceメソッド
は、要素を直接セットに追加するためのメソッドです。
insert
と似ていますが、コンストラクタ引数を直接渡すことができます。
#include <set>
#include <iostream>
int main() {
std::set<int> mySet;
mySet.emplace(1);
mySet.emplace(2);
mySet.emplace(2); // 重複する要素は追加されない
for (int elem : mySet) {
std::cout << elem << " ";
}
return 0;
}
要素の削除
eraseメソッド
eraseメソッド
を使用して、指定した要素を削除します。
#include <set>
#include <iostream>
int main() {
std::set<int> mySet = {1, 2, 3, 4, 5};
mySet.erase(3);
for (int elem : mySet) {
std::cout << elem << " ";
}
return 0;
}
clearメソッド
clearメソッド
を使用して、セット内のすべての要素を削除します。
#include <set>
#include <iostream>
int main() {
std::set<int> mySet = {1, 2, 3, 4, 5};
mySet.clear();
std::cout << "Set size after clear: " << mySet.size() << std::endl;
return 0;
}
要素の検索
findメソッド
findメソッド
を使用して、指定した要素がセットに存在するかどうかを確認します。
存在する場合はその要素へのイテレータを返し、存在しない場合はend
イテレータを返します。
#include <set>
#include <iostream>
int main() {
std::set<int> mySet = {1, 2, 3, 4, 5};
auto it = mySet.find(3);
if (it != mySet.end()) {
std::cout << "Element found: " << *it << std::endl;
} else {
std::cout << "Element not found" << std::endl;
}
return 0;
}
countメソッド
countメソッド
を使用して、指定した要素がセットに存在するかどうかを確認します。
存在する場合は1を返し、存在しない場合は0を返します。
#include <set>
#include <iostream>
int main() {
std::set<int> mySet = {1, 2, 3, 4, 5};
int count = mySet.count(3);
std::cout << "Element count: " << count << std::endl;
return 0;
}
std::setの応用操作
イテレータの使用
beginとend
beginメソッド
とendメソッド
を使用して、セットの要素をイテレートすることができます。
#include <set>
#include <iostream>
int main() {
std::set<int> mySet = {1, 2, 3, 4, 5};
for (auto it = mySet.begin(); it != mySet.end(); ++it) {
std::cout << *it << " ";
}
return 0;
}
rbeginとrend
rbeginメソッド
とrendメソッド
を使用して、セットの要素を逆順にイテレートすることができます。
#include <set>
#include <iostream>
int main() {
std::set<int> mySet = {1, 2, 3, 4, 5};
for (auto it = mySet.rbegin(); it != mySet.rend(); ++it) {
std::cout << *it << " ";
}
return 0;
}
範囲ベースの操作
equal_rangeメソッド
equal_rangeメソッド
は、指定した要素の範囲を取得します。
セットでは、要素は一意であるため、範囲は単一の要素を指します。
#include <set>
#include <iostream>
int main() {
std::set<int> mySet = {1, 2, 3, 4, 5};
auto range = mySet.equal_range(3);
for (auto it = range.first; it != range.second; ++it) {
std::cout << *it << " ";
}
return 0;
}
lower_boundとupper_boundメソッド
lower_boundメソッド
は、指定した要素以上の最初の要素を指すイテレータを返します。
upper_boundメソッド
は、指定した要素より大きい最初の要素を指すイテレータを返します。
#include <set>
#include <iostream>
int main() {
std::set<int> mySet = {1, 2, 3, 4, 5};
auto lower = mySet.lower_bound(3);
auto upper = mySet.upper_bound(3);
std::cout << "Lower bound: " << *lower << std::endl;
std::cout << "Upper bound: " << *upper << std::endl;
return 0;
}
カスタムコンパレータの使用
比較関数を使ったソート
カスタムコンパレータを使用して、セットの要素をソートすることができます。
以下は、降順にソートする例です。
#include <set>
#include <iostream>
bool customCompare(int a, int b) {
return a > b;
}
int main() {
std::set<int, decltype(&customCompare)> mySet(customCompare);
mySet.insert(1);
mySet.insert(2);
mySet.insert(3);
for (int elem : mySet) {
std::cout << elem << " ";
}
return 0;
}
クラスを使ったソート
カスタムコンパレータクラスを使用して、セットの要素をソートすることも可能です。
#include <set>
#include <iostream>
struct CustomCompare {
bool operator()(int a, int b) const {
return a > b;
}
};
int main() {
std::set<int, CustomCompare> mySet;
mySet.insert(1);
mySet.insert(2);
mySet.insert(3);
for (int elem : mySet) {
std::cout << elem << " ";
}
return 0;
}
std::setのパフォーマンスと注意点
std::setの内部構造
赤黒木(Red-Black Tree)
std::set
は内部的に赤黒木(Red-Black Tree)を使用しています。
赤黒木は自己平衡二分探索木の一種で、挿入、削除、検索の操作が平均O(log n)の時間計算量で行えます。
パフォーマンスの考慮点
挿入と削除のコスト
std::set
の挿入と削除の操作は、赤黒木の再バランスが必要になるため、O(log n)の時間計算量がかかります。
検索のコスト
std::set
の検索操作もO(log n)の時間計算量で行えます。
これは、赤黒木の高さが常にO(log n)に保たれるためです。
std::setと他のコンテナの比較
std::set vs std::vector
std::set
は要素の重複を許さず、自動的にソートされる点でstd::vector
と異なります。
std::vector
は要素の順序を保持し、重複を許します。
std::set vs std::unordered_set
std::unordered_set
はハッシュテーブルを使用しており、平均O(1)の時間計算量で挿入、削除、検索が行えますが、要素はソートされません。
一方、std::set
は要素がソートされるため、特定の順序が必要な場合に適しています。
実践例
基本的な使用例
以下は、std::set
の基本的な使用例です。
#include <set>
#include <iostream>
int main() {
std::set<int> mySet = {1, 2, 3, 4, 5};
mySet.insert(6);
mySet.erase(3);
for (int elem : mySet) {
std::cout << elem << " ";
}
return 0;
}
応用的な使用例
ユニークな要素の管理
std::set
を使用して、ユニークな要素を管理する例です。
#include <set>
#include <iostream>
int main() {
std::set<int> mySet;
mySet.insert(1);
mySet.insert(2);
mySet.insert(2); // 重複する要素は追加されない
for (int elem : mySet) {
std::cout << elem << " ";
}
return 0;
}
ソートされたデータの管理
std::set
を使用して、ソートされたデータを管理する例です。
#include <set>
#include <iostream>
int main() {
std::set<int> mySet = {5, 3, 1, 4, 2};
for (int elem : mySet) {
std::cout << elem << " ";
}
return 0;
}
まとめ
std::set
は、重複しない要素を管理し、自動的にソートするための強力なデータ構造です。
基本的な操作から応用的な操作まで、幅広い用途に対応できるため、C++プログラミングにおいて非常に有用です。
この記事を通じて、std::set
の使い方とその特徴を理解し、実際のプログラムに活用してみてください。