Skip to content

C++ STL 알고리즘 완전 가이드

개요 — STL 알고리즘을 써야 하는 이유

Section titled “개요 — STL 알고리즘을 써야 하는 이유”

원시 for 루프 대신 STL 알고리즘을 사용하면 다음 이점이 있습니다.

비교원시 forSTL 알고리즘
의도 표현암묵적함수 이름이 의도를 명시
오류 가능성경계 오류, off-by-one반복자 범위로 안전
최적화수동내부적으로 최적화됨
병렬화 (C++17)직접 구현std::execution::par

대부분의 알고리즘은 <algorithm> 헤더에 있으며, 수치 알고리즘은 <numeric>에 있습니다.


#include <algorithm>
#include <vector>
std::vector<int> Nums = {5, 2, 8, 1, 9, 3};
// 기본 오름차순 정렬 (평균 O(n log n))
std::sort(Nums.begin(), Nums.end());
// {1, 2, 3, 5, 8, 9}
// 내림차순
std::sort(Nums.begin(), Nums.end(), std::greater<int>{});
// {9, 8, 5, 3, 2, 1}
// 람다 비교자
std::vector<std::string> Words = {"banana", "apple", "cherry", "date"};
std::sort(Words.begin(), Words.end(), [](const std::string& A, const std::string& B)
{
return A.size() < B.size(); // 길이 오름차순
});
// {"date", "apple", "banana", "cherry"}

1.2 std::stable_sort — 동일 요소 순서 유지

Section titled “1.2 std::stable_sort — 동일 요소 순서 유지”
struct Player { std::string Name; int Score; };
std::vector<Player> Players = {
{"Alice", 100}, {"Bob", 80}, {"Carol", 100}, {"Dave", 80}
};
// 점수 내림차순, 동점자 원래 순서 유지
std::stable_sort(Players.begin(), Players.end(),
[](const Player& A, const Player& B) { return A.Score > B.Score; });
// Alice(100), Carol(100), Bob(80), Dave(80)

1.3 std::partial_sort — 앞 N개만 정렬

Section titled “1.3 std::partial_sort — 앞 N개만 정렬”
std::vector<int> Data = {5, 2, 8, 1, 9, 3, 7, 4};
// 앞 3개만 정렬 (상위 3개 추출)
std::partial_sort(Data.begin(), Data.begin() + 3, Data.end());
// {1, 2, 3, 8, 9, 7, 5, 4} — 앞 3개만 정렬됨
// nth_element — N번째 요소만 올바른 위치로 (O(n))
std::nth_element(Data.begin(), Data.begin() + 3, Data.end());
// Data[3]에는 정렬 시 4번째가 올 원소가 위치 (나머지는 미정)

std::vector<int> Nums = {1, 5, 3, 8, 2, 7};
// 값으로 검색 (O(n))
auto It = std::find(Nums.begin(), Nums.end(), 8);
if (It != Nums.end())
{
std::cout << "찾음: " << *It << " at index " << std::distance(Nums.begin(), It);
}
// 조건으로 검색
auto BigIt = std::find_if(Nums.begin(), Nums.end(),
[](int N) { return N > 6; }); // 첫 번째 6 초과 원소
// find_if_not — 조건을 만족하지 않는 첫 번째
auto SmallIt = std::find_if_not(Nums.begin(), Nums.end(),
[](int N) { return N < 5; }); // 처음으로 5 이상인 원소

정렬된 범위에서 이진 탐색 (O(log n)).

std::vector<int> Sorted = {1, 2, 3, 5, 8, 13, 21};
// 존재 여부만 확인
bool bFound = std::binary_search(Sorted.begin(), Sorted.end(), 8); // true
// 삽입 위치 반환 (첫 번째 >= 값)
auto LBound = std::lower_bound(Sorted.begin(), Sorted.end(), 6);
// *LBound == 8 (6 이상의 첫 번째 원소)
// 첫 번째 > 값
auto UBound = std::upper_bound(Sorted.begin(), Sorted.end(), 8);
// *UBound == 13
// 동일한 값의 범위 [lower, upper)
auto [Lo, Hi] = std::equal_range(Sorted.begin(), Sorted.end(), 5);
// Lo → 5의 위치, Hi → 5 다음 위치
std::vector<int> Data = {1, 2, 3, 2, 5, 2, 7};
int TwoCount = std::count(Data.begin(), Data.end(), 2); // 3
int EvenCount = std::count_if(Data.begin(), Data.end(),
[](int N) { return N % 2 == 0; }); // 3

std::vector<int> Nums = {1, 2, 3, 4, 5};
std::vector<int> Doubled(Nums.size());
// 단항 변환 — 각 원소에 함수 적용
std::transform(Nums.begin(), Nums.end(), Doubled.begin(),
[](int N) { return N * 2; });
// Doubled = {2, 4, 6, 8, 10}
// 이항 변환 — 두 범위를 합성
std::vector<int> Other = {10, 20, 30, 40, 50};
std::vector<int> Sum(Nums.size());
std::transform(Nums.begin(), Nums.end(), Other.begin(), Sum.begin(),
[](int A, int B) { return A + B; });
// Sum = {11, 22, 33, 44, 55}
std::vector<int> Nums = {1, 2, 3, 4, 5};
// 부수 효과를 위한 순회 (반환값 없음)
std::for_each(Nums.begin(), Nums.end(), [](int& N) { N *= 2; });
// Nums = {2, 4, 6, 8, 10}
// C++17: std::for_each_n — 앞 N개만
std::for_each_n(Nums.begin(), 3, [](int& N) { N = 0; });
// Nums = {0, 0, 0, 8, 10}
std::vector<int> Source = {1, 2, 3, 4, 5, 6, 7, 8};
std::vector<int> Dest;
// 조건에 맞는 원소만 복사
std::copy_if(Source.begin(), Source.end(), std::back_inserter(Dest),
[](int N) { return N % 2 == 0; });
// Dest = {2, 4, 6, 8}

std::vector<int> Nums = {1, 5, 2, 8, 3, 9, 4};
// 조건 true 원소를 앞으로, false를 뒤로 (순서 불안정)
auto Mid = std::partition(Nums.begin(), Nums.end(),
[](int N) { return N % 2 == 0; });
// 짝수들이 앞으로: {2, 4, 8, 5, 3, 9, 1} (순서 보장 안됨)
// Mid는 홀수 시작 반복자
// stable_partition — 상대 순서 유지
auto Mid2 = std::stable_partition(Nums.begin(), Nums.end(),
[](int N) { return N % 2 == 0; });
// {2, 8, 4, 1, 5, 3, 9}
std::vector<int> Nums = {1, 2, 3, 4, 5, 6};
// remove_if는 원소를 실제 삭제하지 않고 뒤로 밀고 새 끝 반복자 반환
auto NewEnd = std::remove_if(Nums.begin(), Nums.end(),
[](int N) { return N % 2 == 0; }); // 짝수 제거
// erase로 실제 삭제 (erase-remove 패턴)
Nums.erase(NewEnd, Nums.end());
// Nums = {1, 3, 5}
// C++20: std::erase_if — 한 줄로
std::erase_if(Nums, [](int N) { return N > 2; });
// Nums = {1}

#include <numeric>
std::vector<int> Nums = {1, 2, 3, 4, 5};
// 합산 (초기값 0)
int Total = std::accumulate(Nums.begin(), Nums.end(), 0); // 15
// 곱셈으로 변경
int Product = std::accumulate(Nums.begin(), Nums.end(), 1,
[](int Acc, int N) { return Acc * N; }); // 120
// 인접 차이
std::vector<int> Diffs(Nums.size());
std::adjacent_difference(Nums.begin(), Nums.end(), Diffs.begin());
// Diffs = {1, 1, 1, 1, 1}
// 누적 합
std::vector<int> Cumulative(Nums.size());
std::inclusive_scan(Nums.begin(), Nums.end(), Cumulative.begin());
// Cumulative = {1, 3, 6, 10, 15}
// iota — 연속 값 채우기
std::vector<int> Seq(10);
std::iota(Seq.begin(), Seq.end(), 0); // {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

6. C++20 Ranges — 선언적 파이프라인

Section titled “6. C++20 Ranges — 선언적 파이프라인”

Ranges는 알고리즘을 파이프라인으로 연결해 더 읽기 쉬운 코드를 작성하게 합니다.

#include <ranges>
#include <vector>
#include <iostream>
std::vector<int> Nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 파이프라인: 짝수만 필터링 → 제곱 → 처음 3개
auto Result = Nums
| std::views::filter([](int N) { return N % 2 == 0; }) // {2, 4, 6, 8, 10}
| std::views::transform([](int N) { return N * N; }) // {4, 16, 36, 64, 100}
| std::views::take(3); // {4, 16, 36}
for (int V : Result)
{
std::cout << V << " "; // 4 16 36
}
// take / drop — 앞 N개 / 건너뛰기
auto First3 = Nums | std::views::take(3); // {1, 2, 3}
auto Skip3 = Nums | std::views::drop(3); // {4, 5, 6, 7, 8, 9, 10}
// iota — 정수 범위 생성 (컨테이너 불필요)
for (int N : std::views::iota(1, 6)) { std::cout << N << " "; } // 1 2 3 4 5
// reverse
for (int N : Nums | std::views::reverse) { std::cout << N << " "; } // 10 9 8 ...
// zip (C++23)
// std::vector<int> A = {1, 2, 3}, B = {4, 5, 6};
// for (auto [a, b] : std::views::zip(A, B)) { ... }
// Ranges 알고리즘 (반복자 쌍 대신 범위 직접 전달)
std::vector<int> Data = {5, 2, 8, 1, 9};
std::ranges::sort(Data); // 정렬
auto It = std::ranges::find(Data, 8); // 검색
bool bAny = std::ranges::any_of(Data, [](int N) { return N > 7; }); // 조건 확인

std::vector<int> Nums = {2, 4, 6, 8};
// 모든 원소가 조건을 만족하는가?
bool bAllEven = std::all_of(Nums.begin(), Nums.end(),
[](int N) { return N % 2 == 0; }); // true
// 하나라도 조건을 만족하는가?
bool bAnyOver5 = std::any_of(Nums.begin(), Nums.end(),
[](int N) { return N > 5; }); // true
// 어떤 원소도 조건을 만족하지 않는가?
bool bNoneNeg = std::none_of(Nums.begin(), Nums.end(),
[](int N) { return N < 0; }); // true

목적함수
정렬sort, stable_sort, partial_sort
검색find, find_if, binary_search, lower_bound
개수count, count_if
변환transform, for_each, copy_if
분할·제거partition, remove_if + erase
수치accumulate, iota, inclusive_scan
조건all_of, any_of, none_of
파이프라인views::filter, views::transform, views::take

핵심 규칙:

  • remove_if는 실제 삭제를 하지 않으므로 반드시 erase와 쌍으로 사용 (erase-remove 패턴)
  • 정렬된 범위에는 binary_search / lower_bound (O(log n)), 미정렬이면 find (O(n))
  • C++20 Ranges는 지연 평가(lazy evaluation) — 순회 전까지 계산하지 않음
  • std::ranges:: 버전은 컨테이너를 직접 전달 가능해 더 간결