C++のstd::setの使い方について詳しく解説

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の使い方とその特徴を理解し、実際のプログラムに活用してみてください。

目次から探す