C++ STLアルゴリズムの使い方と実装例

1. 非変更シーケンス操作

これらのアルゴリズムはコンテナの要素を変更しない。

1.1 find系関数
  • find(first, last, value):値がvalueと等しい最初の要素を検索。
  • find_if(first, last, pred):述語predを満たす最初の要素を検索。
  • find_end(first, last, s_first, s_last):部分列の最後の出現位置を検索。
#include <vector>
#include <algorithm>
#include <iostream>

std::vector<int> data = {10, 20, 30, 40, 50};

auto pos = std::find(data.begin(), data.end(), 30);
if (pos != data.end()) {
    std::cout << "Found: " << *pos << '\n'; // 30
}

auto gt40 = std::find_if(data.begin(), data.end(),
    [](int v) { return v > 40; });
std::cout << "First >40: " << *gt40 << '\n'; // 50

std::vector<int> pattern = {20, 30};
auto sub_pos = std::find_end(data.begin(), data.end(),
                             pattern.begin(), pattern.end());
if (sub_pos != data.end()) {
    std::cout << "Pattern starts at: " << (sub_pos - data.begin()) << '\n'; // 1
}
1.2 count系関数
  • count(first, last, value):指定値の出現回数を返す。
  • count_if(first, last, pred):述語を満たす要素の個数を返す。
std::vector<int> vals = {2, 4, 6, 4, 8, 4};
int fours = std::count(vals.begin(), vals.end(), 4); // 3
int odds = std::count_if(vals.begin(), vals.end(),
    [](int x) { return x % 2 == 1; }); // 0
1.3 for_each

各要素にファンクタを適用する(参照渡しで書き換え可能)。

std::vector<int> numbers = {1, 2, 3};
std::for_each(numbers.begin(), numbers.end(),
    [](int& n) { n *= 10; });
// numbers → {10, 20, 30}
1.4 equal と mismatch
  • equal(a1, a2, b1):2つの範囲が等しいか判定。
  • mismatch(a1, a2, b1):最初に異なる位置のイテレータペアを返す。
std::vector<int> x = {1, 2, 3}, y = {1, 2, 5};
bool eq = std::equal(x.begin(), x.end(), y.begin()); // false

auto diff = std::mismatch(x.begin(), x.end(), y.begin());
if (diff.first != x.end()) {
    std::cout << "Diff: " << *diff.first << " vs " << *diff.second << '\n';
}
1.5 all_of / any_of / none_of

条件を全要素・一部・ゼロ要素が満たすかをチェック。

std::vector<int> evens = {2, 4, 6, 8};
bool all_even = std::all_of(evens.begin(), evens.end(),
    [](int n) { return n % 2 == 0; }); // true
bool has_odd = std::any_of(evens.begin(), evens.end(),
    [](int n) { return n % 2 == 1; }); // false
bool no_neg = std::none_of(evens.begin(), evens.end(),
    [](int n) { return n < 0; }); // true

2. 変更シーケンス操作

これらのアルゴリズムはコンテナの内容を変更する。

2.1 copy系関数
  • copy(src_begin, src_end, dest_begin):全要素をコピー。
  • copy_if(..., pred):条件を満たす要素のみコピー。
std::vector<int> input = {1, 2, 3, 4, 5};
std::vector<int> output(input.size());
std::copy(input.begin(), input.end(), output.begin());

std::vector<int> positives;
std::copy_if(input.begin(), input.end(), std::back_inserter(positives),
    [](int x) { return x > 3; }); // {4, 5}
2.2 transform

要素を変換して別の範囲に格納。

std::vector<int> base = {1, 2, 3};
std::vector<int> squares(base.size());
std::transform(base.begin(), base.end(), squares.begin(),
    [](int x) { return x * x; }); // {1, 4, 9}

std::vector<int> left = {10, 20}, right = {1, 2};
std::vector<int> sum(2);
std::transform(left.begin(), left.end(), right.begin(), sum.begin(),
    std::plus<int>{}); // {11, 22}
2.3 replace系関数
  • replace(..., old, new):特定値を置換。
  • replace_if(..., pred, new):条件を満たす要素を置換。
  • replace_copy(...):元を変更せずに置換結果を出力。
std::vector<int> arr = {1, 0, 2, 0, 3};
std::replace(arr.begin(), arr.end(), 0, -1); // {1, -1, 2, -1, 3}

std::replace_if(arr.begin(), arr.end(),
    [](int x) { return x < 0; }, 99); // {1, 99, 2, 99, 3}

std::vector<int> replaced;
std::replace_copy(arr.begin(), arr.end(), std::back_inserter(replaced),
    99, 0); // {1, 0, 2, 0, 3}
2.4 removeとeraseの組み合わせ

removeは論理削除(末尾に移動)のみ行うため、eraseと併用する必要がある。

std::vector<int> seq = {1, 2, 3, 2, 4, 2};
seq.erase(std::remove(seq.begin(), seq.end(), 2), seq.end());
// {1, 3, 4}

seq = {1, 2, 3, 4, 5};
seq.erase(std::remove_if(seq.begin(), seq.end(),
    [](int x) { return x % 2 == 0; }), seq.end());
// {1, 3, 5}
2.5 unique

連続重複を除去(ソート済み前提)。

std::vector<int> dupes = {1, 1, 2, 2, 2, 3};
dupes.erase(std::unique(dupes.begin(), dupes.end()), dupes.end());
// {1, 2, 3}
2.6 reverse

要素順序を反転。

std::vector<int> rev = {1, 2, 3, 4};
std::reverse(rev.begin(), rev.end()); // {4, 3, 2, 1}
2.7 rotate

指定位置を先頭に回転。

std::vector<int> rot = {1, 2, 3, 4, 5};
std::rotate(rot.begin(), rot.begin() + 2, rot.end());
// {3, 4, 5, 1, 2}
2.8 shuffle

ランダムに並べ替え(C++11以降)。

#include <random>
std::vector<int> deck = {1, 2, 3, 4, 5};
std::shuffle(deck.begin(), deck.end(), std::mt19937{std::random_device{}()});

3. ソート関連アルゴリズム

3.1 sort / stable_sort / partial_sort
  • sort:高速だが不安定。
  • stable_sort:安定ソート(相対順序保持)。
  • partial_sort:先頭k要素だけソート。
std::vector<int> unsorted = {5, 2, 8, 1, 9};
std::sort(unsorted.begin(), unsorted.end()); // 昇順

std::vector<std::pair<int, char>> pairs = {{2,'b'}, {1,'a'}, {2,'c'}};
std::stable_sort(pairs.begin(), pairs.end(),
    [](const auto& a, const auto& b) { return a.first < b.first; });
// {{1,'a'}, {2,'b'}, {2,'c'}} ('b'と'c'の順序維持)

std::vector<int> part = {9, 1, 5, 3, 7, 2};
std::partial_sort(part.begin(), part.begin() + 3, part.end());
// 先頭3要素: {1, 2, 3}(残りは未ソート)
3.2 nth_element

n番目の要素を正しい位置に配置し、左右をパーティション。

std::vector<int> nth = {5, 6, 2, 1, 3, 4};
std::nth_element(nth.begin(), nth.begin() + 2, nth.end());
// nth[2] == 3(全体の3番目に小さい値)
3.3 二分探索関数群

ソート済み範囲での検索(binary_search, lower_bound, upper_bound)。

std::vector<int> sorted = {1, 3, 3, 5, 7};

bool has_three = std::binary_search(sorted.begin(), sorted.end(), 3); // true

auto lb = std::lower_bound(sorted.begin(), sorted.end(), 3); // index 1
auto ub = std::upper_bound(sorted.begin(), sorted.end(), 3); // index 3
3.4 merge

2つのソート済み範囲をマージ。

std::vector<int> a = {1, 4, 7}, b = {2, 3, 5};
std::vector<int> merged(a.size() + b.size());
std::merge(a.begin(), a.end(), b.begin(), b.end(), merged.begin());
// {1, 2, 3, 4, 5, 7}

4. ヒープ操作

STLヒープアルゴリズム(最大ヒープ前提)。

std::vector<int> heap = {1, 3, 2, 4};
std::make_heap(heap.begin(), heap.end()); // {4, 3, 2, 1}

heap.push_back(5);
std::push_heap(heap.begin(), heap.end()); // {5, 4, 2, 1, 3}

std::pop_heap(heap.begin(), heap.end()); // 最大値を末尾へ
int max_elem = heap.back(); // 5
heap.pop_back();

std::sort_heap(heap.begin(), heap.end()); // 昇順ソート

5. 最小・最大値操作

5.1 min / max

2値または初期化リストから極値を取得。

int m = std::min(10, 20); // 10
int M = std::max({3, 1, 4, 1, 5}); // 5
5.2 min_element / max_element

範囲内の極値イテレータを返す。

std::vector<int> nums = {5, 1, 9, 3};
auto min_it = std::min_element(nums.begin(), nums.end()); // → 1
auto max_it = std::max_element(nums.begin(), nums.end()); // → 9
5.3 minmax_element (C++11)

最小・最大を同時に取得。

auto mm = std::minmax_element(nums.begin(), nums.end());
// mm.first → 1, mm.second → 9

6. 数値アルゴリズム (<numeric>)

6.1 accumulate

総和や累積演算。

#include <numeric>
std::vector<int> vals = {1, 2, 3, 4};
int total = std::accumulate(vals.begin(), vals.end(), 0); // 10
int prod = std::accumulate(vals.begin(), vals.end(), 1,
    std::multiplies<int>{}); // 24
6.2 inner_product

内積計算。

std::vector<int> u = {1, 2, 3}, v = {4, 5, 6};
int dot = std::inner_product(u.begin(), u.end(), v.begin(), 0); // 32
6.3 iota

連続整数で埋める。

std::vector<int> seq(5);
std::iota(seq.begin(), seq.end(), 100); // {100, 101, 102, 103, 104}
6.4 partial_sum

累積和を格納。

std::vector<int> src = {1, 2, 3, 4};
std::vector<int> ps(src.size());
std::partial_sum(src.begin(), src.end(), ps.begin()); // {1, 3, 6, 10}
6.5 adjacent_difference

隣接要素の差分を格納。

std::vector<int> diffs(src.size());
std::adjacent_difference(src.begin(), src.end(), diffs.begin());
// {1, 1, 1, 1}

7. その他のアルゴリズム

7.1 generate / generate_n

ジェネレータ関数で範囲を埋める。

std::vector<int> gen(5);
int counter = 0;
std::generate(gen.begin(), gen.end(), [&counter]() { return counter++; });
// {0, 1, 2, 3, 4}

std::vector<int> part_gen(5);
std::generate_n(part_gen.begin(), 3, [&counter]() { return counter++; });
// 先頭3要素更新、残りは未初期化(この例では0)
7.2 includes

ソート済み範囲が部分集合か判定。

std::vector<int> superset = {1, 2, 3, 4, 5};
std::vector<int> subset = {2, 4};
bool is_included = std::includes(superset.begin(), superset.end(),
                                subset.begin(), subset.end()); // true
7.3 集合演算

ソート済み範囲に対する集合操作(和・積・差・対称差)。

std::vector<int> A = {1, 2, 3, 4}, B = {3, 4, 5, 6};
std::vector<int> result;

std::set_union(A.begin(), A.end(), B.begin(), B.end(),
               std::back_inserter(result)); // {1,2,3,4,5,6}

result.clear();
std::set_intersection(A.begin(), A.end(), B.begin(), B.end(),
                      std::back_inserter(result)); // {3,4}

result.clear();
std::set_difference(A.begin(), A.end(), B.begin(), B.end(),
                    std::back_inserter(result)); // {1,2}

result.clear();
std::set_symmetric_difference(A.begin(), A.end(), B.begin(), B.end(),
                              std::back_inserter(result)); // {1,2,5,6}

8. よくある質問

Q: sortstable_sortの違いは?
A: sortはIntrosort(クイックソート+ヒープソート)で不安定だが高速。stable_sortはマージソートベースで安定(同一要素の相対順序を保持)だがメモリ使用量が多い。

Q: なぜremoveeraseと組み合わせる必要があるのか?
A: removeは「削除」ではなく「上書き移動」を行うため、コンテナサイズは変わらない。物理的に削除するにはeraseで末尾の不要要素を消す必要がある(イディオム:v.erase(remove(...), v.end()))。

Q: どのアルゴリズムがソート済み入力を要求するか?
A: 二分探索系(binary_searchなど)、集合演算(set_unionなど)、mergeincludesなど。これらはO(log n)やO(n)の効率を達成するために入力の順序に依存している。

タグ: C++ STL Algorithm Vector

6月4日 19:07 投稿