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

1. 비수정 시퀀스 알고리즘

이러한 알고리즘은 작업 대상 컨테이너의 요소를 변경하지 않습니다.

1.1 find와 find_if

  • find(begin, end, value): value와 동일한 첫 번째 요소를 찾아 반복자 반환 (못 찾으면 end 반환)
  • find_if(begin, end, predicate): 조건자를 만족하는 첫 번째 요소 찾기
  • find_end(begin, end, sub_begin, sub_end): 하위 시퀀스가 마지막으로 나타나는 위치 찾기
std::vector<int> data = {2, 4, 6, 8, 10};

// 값이 6인 요소 찾기
auto pos = std::find(data.begin(), data.end(), 6);
if (pos != data.end()) {
    std::cout << "발견: " << *pos << std::endl;  // 출력: 6
}

// 5보다 큰 첫 번째 요소 찾기
auto pos2 = std::find_if(data.begin(), data.end(), [](int n) {
    return n > 5;
});
std::cout << "5보다 큰 첫 번째: " << *pos2 << std::endl;  // 출력: 6

// 하위 시퀀스 찾기
std::vector<int> pattern = {4, 6};
auto pos3 = std::find_end(data.begin(), data.end(), pattern.begin(), pattern.end());
if (pos3 != data.end()) {
    std::cout << "하위 시퀀스 시작 인덱스: " << pos3 - data.begin() << std::endl;  // 출력: 1
}

1.2 count와 count_if

  • count(begin, end, value): value와 동일한 요소 개수 계산
  • count_if(begin, end, predicate): 조건자를 만족하는 요소 개수 계산
std::vector<int> collection = {5, 3, 1, 3, 7, 3};
int target = std::count(collection.begin(), collection.end(), 3); // 3의 개수, 결과: 3
int odd_cnt = std::count_if(collection.begin(), collection.end(), [](int n) { 
    return n % 2 != 0; 
}); // 홀수 개수, 결과: 5

1.3 for_each

범위의 각 요소에 함수 적용

std::vector<int> items = {10, 20, 30, 40, 50};
std::for_each(items.begin(), items.end(), [](int& val) { 
    val += 5; // 각 요소에 5 더하기
});
// 이제 items는 {15, 25, 35, 45, 50}

1.4 equal과 mismatch

  • equal(b1, e1, b2): 두 범위가 같은지 비교
  • mismatch(b1, e1, b2): 첫 번째로 일치하지 않는 요소의 반복자 쌍 반환
std::vector<int> source = {1, 2, 3};
std::vector<int> target1 = {1, 2, 4};
std::vector<int> target2 = {1, 2, 3, 4};

// source와 target1 비교
bool same = std::equal(source.begin(), source.end(), target1.begin());
std::cout << "source == target1? " << std::boolalpha << same << std::endl;  // 출력: false

// source와 target2의 첫 번째 불일치 찾기
auto diff = std::mismatch(source.begin(), source.end(), target2.begin());
if (diff.first != source.end()) {
    std::cout << "불일치: " << *diff.first << " vs " << *diff.second << std::endl;
}

1.5 all_of, any_of, none_of

범위 내 요소가 모두, 하나라도, 또는 하나도 조건을 만족하지 않는지 확인

std::vector<int> numbers = {2, 4, 6, 8};
bool all_positive = std::all_of(numbers.begin(), numbers.end(), [](int n) { 
    return n > 0; 
}); // true
bool any_zero = std::any_of(numbers.begin(), numbers.end(), [](int n) { 
    return n == 0; 
}); // false
bool none_negative = std::none_of(numbers.begin(), numbers.end(), [](int n) { 
    return n < 0; 
}); // true

2. 수정 시퀀스 알고리즘

이 알고리즘들은 작업 대상 컨테이너의 요소를 수정합니다.

2.1 copy와 copy_if

  • copy(begin, end, dest): [begin, end)의 요소를 dest 위치에 복사
  • copy_if(begin, end, dest, predicate): 조건자를 만족하는 요소만 복사
std::vector<int> source = {1, 2, 3, 4, 5};
std::vector<int> destination(5);  // 충분한 공간 사전 할당 필요

// 모든 요소 복사
std::copy(source.begin(), source.end(), destination.begin());  // destination: [1,2,3,4,5]

// 홀수 요소만 새 컨테이너에 복사
std::vector<int> odds;
std::copy_if(source.begin(), source.end(), std::back_inserter(odds), [](int n) {
    return n % 2 != 0;
});  // odds: [1,3,5]

참고: back_inserter(dest)는 자동으로 push_back을 호출하므로 공간을 미리 할당할 필요가 없습니다.

2.2 transform

범위의 각 요소에 함수를 적용하고 결과를 다른 범위에 저장

std::vector<int> values = {1, 2, 3};
std::vector<int> squares(3);

// 제곱 계산 (단일 인자 변환)
std::transform(values.begin(), values.end(), squares.begin(), [](int n) {
    return n * n;
});  // squares: [1,4,9]

// 두 컨테이너 요소 더하기 (이중 인자 변환)
std::vector<int> left = {1, 2, 3};
std::vector<int> right = {4, 5, 6};
std::vector<int> result(3);
std::transform(left.begin(), left.end(), right.begin(), result.begin(), [](int x, int y) {
    return x + y;
});  // result: [5,7,9]

2.3 replace, replace_if와 replace_copy

  • replace(begin, end, old_val, new_val): 모든 old_valnew_val로 교체
  • replace_if(begin, end, predicate, new_val): 조건자를 만족하는 요소 교체
  • replace_copy(begin, end, dest, old_val, new_val): 복사 시 교체 (원본 불변)
std::vector<int> numbers = {1, 2, 3, 2, 5};

// 모든 2를 20으로 교체
std::replace(numbers.begin(), numbers.end(), 2, 20);  // numbers: [1,20,3,20,5]

// 15보다 큰 요소를 0으로 교체
std::replace_if(numbers.begin(), numbers.end(), [](int n) {
    return n > 15;
}, 0);  // numbers: [1,0,3,0,5]

// 복사 시 3을 300으로 교체 (원본 불변)
std::vector<int> output;
std::replace_copy(numbers.begin(), numbers.end(), std::back_inserter(output), 3, 300);  // output: [1,0,300,0,5]

2.4 remove, remove_if와 erase

  • remove(begin, end, value): value와 같은 요소를 끝으로 "이동", 새 논리적 끝 반복자 반환 (실제 삭제X, erase와 함께 사용)
  • remove_if(begin, end, predicate): 조건자를 만족하는 요소를 끝으로 이동
std::vector<int> values = {1, 2, 3, 2, 4};

// 논리적 삭제 (끝으로 이동)
auto new_end = std::remove(values.begin(), values.end(), 2);  // values: [1,3,4,2,2]

// 물리적 삭제 (실제 요소 제거)
values.erase(new_end, values.end());  // values: [1,3,4]

// lambda와 결합하여 홀수 삭제
values = {1, 2, 3, 4, 5};
values.erase(std::remove_if(values.begin(), values.end(), [](int n) {
    return n % 2 != 0;
}), values.end());  // values: [2,4]

2.5 unique

범위 내 연속된 중복 요소를 제거하고 새 논리적 끝 반복자 반환. 보통 erase와 함께 사용.

std::vector<int> sequence = {1, 1, 2, 2, 3, 3, 3, 4, 5};
auto new_last = std::unique(sequence.begin(), sequence.end());
sequence.erase(new_last, sequence.end()); // sequence는 {1, 2, 3, 4, 5}

2.6 reverse

범위 내 요소 순서 뒤집기

std::vector<int> list = {1, 2, 3, 4, 5};
std::reverse(list.begin(), list.end()); // list는 {5, 4, 3, 2, 1}

2.7 rotate

범위 내 요소를 회전시켜 중간 요소가 새로운 첫 번째 요소가 되도록 함

std::vector<int> list = {1, 2, 3, 4, 5};
std::rotate(list.begin(), list.begin() + 2, list.end()); // 3을 시작점으로 회전, list는 {3, 4, 5, 1, 2}

2.8 shuffle

범위 내 요소를 무작위로 재배열 (C++11 이상 필요)

#include <random>
#include <algorithm>

std::vector<int> list = {1, 2, 3, 4, 5};
std::random_device rd;
std::mt19937 gen(rd());
std::shuffle(list.begin(), list.end(), gen); // list의 요소를 무작위로 섞음

3. 정렬 및 관련 알고리즘

3.1 sort, stable_sort와 partial_sort

  • sort(begin, end): 빠른 정렬 (비안정, 평균 시간 복잡도 O(n log n))
  • stable_sort(begin, end): 안정 정렬 (동일 요소 상대 위치 불변)
  • partial_sort(begin, mid, end): 부분 정렬로 [begin, mid)가 전체 중 가장 작은 정렬된 요소
std::vector<int> numbers = {5, 3, 1, 4, 2};
std::sort(numbers.begin(), numbers.end()); // 기본 오름차순, numbers: {1, 2, 3, 4, 5}
std::sort(numbers.begin(), numbers.end(), std::greater<int>()); // 내림차순, numbers: {5, 4, 3, 2, 1}
std::sort(numbers.begin(), numbers.end(), [](int a, int b) { 
    return a < b; 
}); // 오름차순, 사용자 정의 비교

std::vector<std::pair<int, int>> pairs = {{1, 2}, {2, 1}, {1, 1}, {2, 2}};
std::stable_sort(pairs.begin(), pairs.end(), [](const auto& a, const auto& b) {
    return a.first < b.first; // first로 정렬, 동일 요소의 상대 순서 유지
});

std::vector<int> numbers = {5, 3, 1, 4, 2, 6};
// 가장 작은 3개 요소를 앞에 배치하고 정렬
std::partial_sort(numbers.begin(), numbers.begin() + 3, numbers.end());
// 이제 numbers 앞 세 요소는 1, 2, 3, 뒤에는 미정렬된 4, 5, 6

3.2 nth_element

범위를 재배열하여 지정 위치의 요소가 정렬 후 해당 위치의 값이 되도록 하고, 왼쪽 요소는 모두 작거나 같고 오른쪽 요소는 모두 크거나 같도록 함

std::vector<int> numbers = {5, 3, 1, 4, 2, 6};
// 세 번째로 작은 요소 찾기 (인덱스 2)
std::nth_element(numbers.begin(), numbers.begin() + 2, numbers.end());
// 이제 numbers[2]는 3, 왼쪽 요소는 <= 3, 오른쪽 요소는 >= 3

3.3 binary_search, lower_bound, upper_bound

정렬된 컨테이너에서만 사용 가능

  • binary_search(begin, end, value): value是否存在 (bool 반환)
  • lower_bound(begin, end, value): value 이상이 첫 번째 요소 반복자 반환
  • upper_bound(begin, end, value): value 초과 첫 번째 요소 반복자 반환
std::vector<int> sorted = {1, 3, 3, 5, 7};  // 먼저 정렬 필요

// 3이是否存在
bool exists = std::binary_search(sorted.begin(), sorted.end(), 3);  // true

// 첫 번째 >= 3 요소 찾기
auto lower = std::lower_bound(sorted.begin(), sorted.end(), 3);
std::cout << "lower_bound 인덱스: " << lower - sorted.begin() << std::endl;  // 출력: 1

// 첫 번째 > 3 요소 찾기
auto upper = std::upper_bound(sorted.begin(), sorted.end(), 3);
std::cout << "upper_bound 인덱스: " << upper - sorted.begin() << std::endl;  // 출력: 3

3.4 merge

정렬된 범위를 새 컨테이너로 병합 (정렬 유지)

std::vector<int> left = {1, 3, 5};
std::vector<int> right = {2, 4, 6};
std::vector<int> merged(left.size() + right.size());

// left와 right 병합 (둘 다 정렬 필요)
std::merge(left.begin(), left.end(), right.begin(), right.end(), merged.begin());  // merged: [1,2,3,4,5,6]

4. 힙 알고리즘

STL은 범위를 힙으로 조작하는 알고리즘을 제공합니다: make_heap, push_heap, pop_heap, sort_heap

std::vector<int> heap_data = {4, 1, 3, 2, 5};
std::make_heap(heap_data.begin(), heap_data.end()); // 최대 힙 구성, heap_data: {5, 4, 3, 2, 1}

heap_data.push_back(6);
std::push_heap(heap_data.begin(), heap_data.end()); // 새 요소를 힙에 추가, heap_data: {6, 4, 5, 2, 1, 3}

std::pop_heap(heap_data.begin(), heap_data.end()); // 최대 요소를 끝으로 이동, heap_data: {5, 4, 3, 2, 1, 6}
int top = heap_data.back(); // 최대 요소 6 획득
heap_data.pop_back(); // 최대 요소 제거

std::sort_heap(heap_data.begin(), heap_data.end()); // 힙을 오름차순으로 정렬, heap_data: {1, 2, 3, 4, 5}

5. 최소/최대 알고리즘

5.1 min과 max

두 값 또는 초기화 목록에서 최소/최대 값 반환

int x = 5, y = 3;
int minimum = std::min(x, y); // 3
int maximum = std::max(x, y); // 5

auto min_of_init = std::min({4, 2, 8, 5, 1}); // 1
auto max_of_init = std::max({4, 2, 8, 5, 1}); // 8

5.2 min_element와 max_element

범위 내 최소/최대 요소의 반복자 반환

std::vector<int> data = {3, 1, 4, 2, 5};
auto min_pos = std::min_element(data.begin(), data.end()); // 1 가리킴
auto max_pos = std::max_element(data.begin(), data.end()); // 5 가리킴

5.3 minmax_element (C++11)

범위 내 최소와 최대 요소를 동시에 반환

std::vector<int> data = {3, 1, 4, 2, 5};
auto result = std::minmax_element(data.begin(), data.end());
// result.first는 1 가리킴, result.second는 5 가리킴

6. 수치 알고리즘 (<numeric> 헤더)

6.1 accumulate

범위 내 요소의 누적 합계 (또는 사용자 정의 연산) 계산

#include <numeric>

std::vector<int> data = {1, 2, 3, 4, 5};
int total = std::accumulate(data.begin(), data.end(), 0); // 합계, 초기값 0, 결과 15
int product = std::accumulate(data.begin(), data.end(), 1, std::multiplies<int>()); // 곱, 초기값 1, 결과 120

6.2 inner_product

두 범위의 내적 (또는 사용자 정의 연산) 계산

std::vector<int> a = {1, 2, 3};
std::vector<int> b = {4, 5, 6};
int dot = std::inner_product(a.begin(), a.end(), b.begin(), 0); // 1*4 + 2*5 + 3*6 = 32

6.3 iota

연속적으로 증가하는 값으로 범위 채우기

std::vector<int> vec(5);
std::iota(vec.begin(), vec.end(), 10); // 10, 11, 12, 13, 14로 채움

6.4 partial_sum

부분 합 계산하고 결과를 대상 범위에 저장

std::vector<int> source = {1, 2, 3, 4, 5};
std::vector<int> dest(source.size());
std::partial_sum(source.begin(), source.end(), dest.begin()); // dest: {1, 3, 6, 10, 15}

6.5 adjacent_difference

인접 요소의 차이를 계산하고 결과를 대상 범위에 저장

std::vector<int> source = {1, 2, 3, 4, 5};
std::vector<int> dest(source.size());
std::adjacent_difference(source.begin(), source.end(), dest.begin()); // dest: {1, 1, 1, 1, 1}

7. 기타 알고리즘

7.1 generate

생성 함수로 범위 채우기

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

7.2 generate_n

생성 함수로 범위의 시작 n개 요소 채우기

std::vector<int> vec(5);
int num = 10;
std::generate_n(vec.begin(), 3, [&num]() { 
    return num++; 
}); // 처음 세 요소는 10, 11, 12, 나머지 두 요소는 변경되지 않음

7.3 includes

정렬된 범위가 다른 정렬된 범위의 모든 요소를 포함하는지 확인

std::vector<int> container1 = {1, 2, 3, 4, 5};
std::vector<int> container2 = {2, 4};
bool has_all = std::includes(container1.begin(), container1.end(), container2.begin(), container2.end()); // true

7.4 set_union, set_intersection, set_difference, set_symmetric_difference

집합 연산 수행: 합집합, 교집합, 차집합, 대칭 차집합

std::vector<int> set_a = {1, 2, 3, 4, 5};
std::vector<int> set_b = {3, 4, 5, 6, 7};
std::vector<int> output;

// 합집합
std::set_union(set_a.begin(), set_a.end(), set_b.begin(), set_b.end(), std::back_inserter(output));
// output: {1, 2, 3, 4, 5, 6, 7}

// 교집합
output.clear();
std::set_intersection(set_a.begin(), set_a.end(), set_b.begin(), set_b.end(), std::back_inserter(output));
// output: {3, 4, 5}

// 차집합 (set_a - set_b)
output.clear();
std::set_difference(set_a.begin(), set_a.end(), set_b.begin(), set_b.end(), std::back_inserter(output));
// output: {1, 2}

// 대칭 차집합 (set_a ∪ set_b - set_a ∩ set_b)
output.clear();
std::set_symmetric_difference(set_a.begin(), set_a.end(), set_b.begin(), set_b.end(), std::back_inserter(output));
// output: {1, 2, 6, 7}

8. 자주 묻는 질문

  1. sortstable_sort의 차이점은?
    • sort는 빠른 정렬 (실제로는 introsort 알고리즘) 사용, 비안정 (동일 요소 상대 위치 변경 가능), 평균 시간 복잡도 O(n log n)
    • stable_sort는 병합 정렬 사용, 안정 (동일 요소 상대 위치 불변), 시간 복잡도 O(n log n), 약간의 공간 오버헤드 존재
  2. remove 알고리즘은 erase와 함께 사용해야 하는가?
    remove 알고리즘의 원리는 삭제할 요소를 "덮어쓰기"하여 유지할 요소를前面으로 이동시키고 새 논리적 끝 반복자를 반환하지만, 컨테이너의 실제 크기를 수정하지 않습니다. erase는 반복자 범위로 실제 요소를 삭제하고 컨테이너 크기를 수정합니다. 따라서 함께 사용해야 합니다: container.erase(std::remove(...), container.end())
  3. 어떤 알고리즘들이 정렬된 컨테이너가 필요한가?
    이진 탐색 系列 (binary_search, lower_bound, upper_bound), 집합 算法 (set_intersection, set_union 등), merge 등 - 이러한 알고리즘들은 효율한 작업 (예: 이진 탐색 O(log n))을 위해 정렬된 상태를 필요로 합니다.

태그: C++ STL algorithm standard-library template

6월 1일 02:14에 게시됨