C++: STL アルゴリズム
レッスン34では STL コンテナを学びました、データを格納するだけ。
でもコンテナに対して操作が必要 —— 検索、ソート、変換……
自分で書くとバグが起きやすい;STL アルゴリズムを使うと、簡潔・安全。
1. STL アルゴリズムの概要
(1) 1.1 STL アルゴリズムとは?
STL アルゴリズムは C++ 標準ライブラリの汎用関数テンプレート、コンテナの型に依存しない。
STL アルゴリズムを使うメリット:
| 自分で書く | STL アルゴリズム |
|---|---|
| ループを書く必要がある | コードが簡潔 |
| バグが起きやすい | テスト済み |
| パフォーマンスが不安 | 最適化済み |
| コードが読みにくい | コードが読みやすい |
クラス分け:
- 自分で書くアルゴリズム = (車輪の再発明)
- STL アルゴリズム = (標準ライブラリ)
(2) 1.2 アルゴリズムのヘッダファイル
STL アルゴリズムは algorithm ヘッダファイルにある、数値アルゴリズムは numeric。
▶ サンプル 2:STL アルゴリズムの活用(難易度 ⭐)
#include <algorithm> // アルゴリズム用
#include <numeric> // 数値演算(accumulate)
(3) 1.3 アルゴリズムの分類
STL アルゴリズムは関数の種類で分類:
| 分類 | アルゴリズム | 説明 |
|---|---|---|
| 非変更アルゴリズム | find、count、for_each |
コンテナを変更しない |
| 変更アルゴリズム | copy、transform、replace |
コンテナを変更 |
| ソートアルゴリズム | sort、stable_sort、partial_sort |
ソート |
| 検索アルゴリズム | binary_search、lower_bound |
ソート済みコンテナで検索 |
| マージアルゴリズム | merge、inplace_merge |
2つのソート済み範囲をマージ |
| 数値アルゴリズム | accumulate、inner_product |
数値計算 |
| 集合アルゴリズム | set_union、set_intersection |
集合演算 |
2. 非変更アルゴリズム
(1) 2.1 find —— 検索
機能: コンテナで値を検索、イテレータを返す。
関数シグネチャ:
InputIt find(InputIt first, InputIt last, const T& value);
例:値を検索(難易度 ⭐)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> scores = {85, 92, 78, 90, 88};
// 90を検索
auto it = std::find(scores.begin(), scores.end(), 90);
if (it != scores.end()) {
// 見つかった(インデックスを計算)
int index = std::distance(scores.begin(), it);
std::cout << "90が見つかりました、位置:" << index << std::endl;
} else {
std::cout << "90が見つかりませんでした" << std::endl;
}
return 0;
}
出力:
TEXT 📖 参照専用90が見つかりました、位置:3
- 見つからない場合
last(つまりend())を返す - 時間計算量:O(n)
(2) 2.2 count —— カウント
機能: コンテナで指定した値の個数をカウント。
例:カウント(難易度 ⭐)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> scores = {100, 85, 100, 92, 78, 100};
// 満点の数(100点)
int perfect = std::count(scores.begin(), scores.end(), 100);
std::cout << "満点の数:" << perfect << std::endl; // 出力:3
return 0;
}
(3) 2.3 for_each —— 各要素に処理
機能: コンテナの各要素に関数を適用。
例:各要素に処理(難易度 ⭐)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> scores = {85, 92, 78, 90, 88};
// ラムダで出力
std::for_each(scores.begin(), scores.end(), [](int s) {
std::cout << s << " ";
});
std::cout << std::endl;
return 0;
}
出力:
TEXT 📖 参照専用85 92 78 90 88
for_eachは手書きループより簡潔- ラムダと組み合わせる
3. 変更アルゴリズム
(1) 3.1 copy —— コピー
機能: 範囲の要素をコピー。
例:配列をコピー(難易度 ⭐)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dst(5); // あらかじめサイズ確保、5要素
// コピー
std::copy(src.begin(), src.end(), dst.begin());
// 出力
for (int x : dst) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
(2) 3.2 transform —— 変換
機能: 範囲の要素を変換。
例:変換(難易度 ⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> scores = {85, 92, 78, 90, 88};
std::vector<int> adjusted(scores.size()); // 結果用
// 70%を残し、30%を補正
std::transform(scores.begin(), scores.end(), adjusted.begin(),
[](int s) { return s * 0.7 + 90 * 0.3; });
std::cout << "補正後:";
for (int x : adjusted) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
出力:
TEXT 📖 参照専用補正後:86.5 91.9 81.6 90 88.6
(3) 3.3 replace —— 置換
機能: コンテナで指定した値の要素を別の値に置換。
例:置換(難易度 ⭐)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> scores = {85, 92, 78, 90, 88};
// 不合格(<60)を60に置換(補正)
std::replace_if(scores.begin(), scores.end(),
[](int s) { return s < 60; },
60);
std::cout << "補正後:";
for (int x : scores) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
4. ソートアルゴリズム
(1) 4.1 sort —— ソート
機能: コンテナをソート(デフォルトは昇順)。
例:ソート(難易度 ⭐)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> scores = {85, 92, 78, 90, 88};
// 昇順ソート
std::sort(scores.begin(), scores.end());
std::cout << "昇順:";
for (int x : scores) {
std::cout << x << " ";
}
std::cout << std::endl;
// 降順ソート
std::sort(scores.begin(), scores.end(), std::greater<int>());
std::cout << "降順:";
for (int x : scores) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
出力:
TEXT 📖 参照専用昇順:78 85 88 90 92 降順:92 90 88 85 78
(2) 4.2 カスタムソート
例:点数でソート(難易度 ⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
struct Student {
std::string name;
int score;
};
int main() {
std::vector<Student> students = {
{"Alice", 85},
{"Bob", 92},
{"Charlie", 78}
};
// 点数の降順でソート
std::sort(students.begin(), students.end(),
[](const Student& a, const Student& b) {
return a.score > b.score;
});
std::cout << "ランキング:" << std::endl;
for (const auto& s : students) {
std::cout << s.name << ":" << s.score << std::endl;
}
return 0;
}
出力:
TEXT 📖 参照専用ランキング: Bob:92 Alice:85 Charlie:78
5. 数値アルゴリズム
(1) 5.1 accumulate —— 累積
機能: 範囲の要素の和を計算。
例:累積(難易度 ⭐)
#include <iostream>
#include <vector>
#include <numeric>
int main() {
std::vector<int> scores = {85, 92, 78, 90, 88};
// 合計点
int total = std::accumulate(scores.begin(), scores.end(), 0);
std::cout << "合計点:" << total << std::endl; // 出力:433
std::cout << "平均点:" << total / 5.0 << std::endl; // 出力:86.6
return 0;
}
(2) 5.2 inner_product —— 内積
例:2つの vector の内積(難易度 ⭐⭐)
#include <iostream>
#include <vector>
#include <numeric>
int main() {
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = {4, 5, 6};
// 内積:1*4 + 2*5 + 3*6 = 32
int dot_product = std::inner_product(v1.begin(), v1.end(), v2.begin(), 0);
std::cout << "内積:" << dot_product << std::endl; // 出力:32
return 0;
}
6. 総合例
▶ サンプル 1:成績分析システム(難易度 ⭐⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <string>
int main() {
std::vector<int> scores = {85, 92, 78, 90, 88, 76, 95, 83, 89, 91};
// 1. 学生数
int count = scores.size();
std::cout << "学生数:" << count << std::endl;
// 2. 合計と平均
int total = std::accumulate(scores.begin(), scores.end(), 0);
double average = static_cast<double>(total) / count;
std::cout << "合計点:" << total << "、平均点:" << average << std::endl;
// 3. 最高と最低を検索
int max_score = *std::max_element(scores.begin(), scores.end());
int min_score = *std::min_element(scores.begin(), scores.end());
std::cout << "最高点:" << max_score << "、最低点:" << min_score << std::endl;
// 4. 合格者数
int passed = std::count_if(scores.begin(), scores.end(),
[](int s) { return s >= 60; });
std::cout << "合格者数:" << passed << std::endl;
// 5. ソートして上位3名を出力
std::vector<int> top3 = scores;
std::sort(top3.begin(), top3.end(), std::greater<int>());
std::cout << "上位3名:";
for (int i = 0; i < 3; i++) {
std::cout << top3[i] << " ";
}
std::cout << std::endl;
return 0;
}
出力:
TEXT 📖 参照専用学生数:10 合計点:867、平均点:86.7 最高点:95、最低点:76 合格者数:10 上位3名:95 92 91
7. アルゴリズムの使い方
(1) 7.1 イテレータ関数
| 関数 | 機能 |
|---|---|
std::distance(first, last) |
イテレータの距離 |
std::advance(it, n) |
イテレータを n 進める |
std::next(it) |
次のイテレータを返す |
std::prev(it) |
前のイテレータを返す |
(2) 7.2 ラムダの詳細
ラムダは STL アルゴリズムの強力なツール:
// 基本構文
[capture](parameters) -> return_type { body }
// 例:複数条件でソート
std::sort(students.begin(), students.end(),
[](const Student& a, const Student& b) {
if (a.score != b.score)
return a.score > b.score; // 点数の降順
return a.name < b.name; // 名前の昇順
});
▶ サンプル 3:削除パターン(難易度 ⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> scores = {85, 45, 92, 58, 78, 30, 95};
// 60点未満を削除(remove-erase イディオム)
auto new_end = std::remove_if(scores.begin(), scores.end(),
[](int s) { return s < 60; });
scores.erase(new_end, scores.end());
std::cout << "合格者: ";
for (int s : scores) {
std::cout << s << " ";
}
std::cout << std::endl;
return 0;
}
出力:
TEXT 📖 参照専用合格者: 85 92 78 95
❓ よくある質問
Q: STL アルゴリズムと手書きループの違いは? A: STL アルゴリズムを使うメリット:
- 最適化済み
- コンテナの型に依存しない
- コンパイラの最適化が効きやすい
Q: どのコンテナでも STL アルゴリズムを使える? A: はい、でも注意:
- 順序コンテナ(
vector、deque):全部使える- 連想コンテナ(
set、map):メンバー関数がある、メンバー関数を使う
Q: ラムダとは? A: ラムダは C++11 のインライン関数、関数の中で関数を定義。
基本構文:
[capture](params) -> return_type { body }
例:
auto add = [](int a, int b) { return a + b; };
std::cout << add(3, 5) << std::endl; // 出力:8
Q: アルゴリズムでよくあるエラーは? A: よくあるエラー:
- コンテナがソートされていないのに検索アルゴリズムを使う → ソート必須
- 出力先のコンテナが小さい →
back_inserterを使う- イテレータの型が違う → コンテナに合わせる
📖 まとめ
| 分類 | アルゴリズム |
|---|---|
| STL アルゴリズム | 汎用関数テンプレート、コンテナに依存しない |
| 非変更アルゴリズム | find、count、for_each |
| 変更アルゴリズム | copy、transform、replace |
| ソートアルゴリズム | sort + カスタム関数 |
| 数値アルゴリズム | accumulate(累積) |
| ラムダ | インライン関数、アルゴリズムで活用 |
推奨:
- STL アルゴリズムを使う、手書きループを避ける
- アルゴリズムの詳細はドキュメントを確認、自分で書かない
- ラムダを使う、コードが簡潔になる
📝 練習問題
-
初級(難易度 ⭐):
std::vector<int>に10個のランダムな整数を入れ、sortでソートして出力、reverseで逆順に出力。 -
中級(難易度 ⭐⭐):
findでstd::vector<std::string>から文字列を検索、countで特定の値の個数をカウント。 -
上級(難易度 ⭐⭐⭐):
remove_ifとラムダで「vector から奇数を削除」を実装。erase-remove の使い方を理解。
次のレッスン: 練習:OOP総合(#36) —— オブジェクト指向で学生管理システムをリファクタリング