10 minute read

‘뇌를 자극하는 C++ STL - 공동환’ 책 및 c++ reference를 참고하여 작성한 포스트입니다.

원소를 수정하지 않는 알고리즘

  • nonmodifying algorithms은 원소의 순서나 원소의 값을 변경하지 않고 원소를 읽기만 하는 알고리즘이다.

adjacent_find(b, e, x)

  • 구간에서 현재 원소와 다음 원소가 같아지는 첫 원소 반복자 반환
  • 조건을 넣고 싶다면 adjacent_find(b,e,f)를 사용한다.
    • f는 이항 조건자이다.

count(b, e, x)

  • 구간에서 x 원소의 개수 리턴
  • 조건을 넣고 싶다면 count_if(b,e,f)
    • f는 단항 조건자

equal(b, e, b2)

  • [b, e), [b2, b2+(e-b))의 순차열이 같은지 판단
  • 조건을 넣고 싶다면 equal(b, e, b2, f)
    • f는 이항 조건자

find(b,e,x)

  • 구간에서 x를 찾고 반복자를 리턴한다.
  • 조건을 넣고 싶다면 find_if(b, e, x, f);
    • f는 단항 조건자

find_end(b, e, b2, e2)

  • 일치하는 순차열 구간이 여러 개라면 마지막 순차열의 첫 원소 반복자 리턴

for_each(b, e, f)

  • 구간 내 모든 원소에 대해 f 적용
    • f는 단항 연산자
  • 원소 수정도 가능하다

lexicographical_compare(b, e, b2, e2)

  • 순차열을 사전식 비교한다. 디폴트는 less.

min(a,b), max(a,b)

  • 두 값 비교하여 크거나 작은 값 리턴
  • 조건을 넣고 싶으면 min(a,b,f), max(a,b,f) 사용하면 된다.

max_element(b, e), max_element(b,e,f)

  • 구간 가장 크거나 작은 원소의 반복자를 반환함.
  • 조건을 넣고 싶다면 max_element(b, e, f), min_element(b,e,f)알고리즘은 작은 원소의 반복자 반환.
    • f는 이항 조건자

mismatch(b, e, b2)

  • 구간 [b,e)과 구간 [b2, b2+(e-b))의 모든 원소를 하나씩 비교하여 원소 값이 서로 다른 첫 원소의 반복자 쌍을 반환한다.
  • 조건자를 추가한다면 두 구간의 반복자 p, q 에 대하여 !f(*p, *q)가 참인 첫 원소의 반복자를 반환한다.

search_n(b,e,n,x)

  • 구간 [b, e)의 순차열에서 원소 x가 n번 연속한 첫 원소의 반복자를 반환한다.
struct Pred1
{
	bool operator()(int a) {
		return 3 < a;
	}
};

bool Pred2(int left, int right) {
	return abs(left - right) < 4;
}

class PrintFunctor
{
public:
	PrintFunctor(char c = ' ') : fmt(c) { }
	void operator()(int n) {
		cout << n << fmt;
	}	
private:
	char fmt;
};

struct Point
{
	Point(int _x = 0, int _y = 0) : x(_x), y(_y) { }
	void print() {cout << "[" << x << ","<<y<<"]" ;}
	int x;
	int y;
};

bool PointPred(const Point& a, const Point& b) {
	return a.y < b.y;
}

int main()
{
	vector<int> v1 {1, 2, 2, 3, 4, 5};
	vector<int> v2 {3, 4, 5};
	auto iter1 = adjacent_find(v1.begin(), v1.end());
	cout << "iter-1 : " << *(iter1-1) << ", iter : " << *iter1 << ", iter+1 : " << *(iter1+1) << endl;
	cout << "count(v1.begin(), v1.end(), 2) : " << count(v1.begin(), v1.end(), 2) << endl;
	cout << "count_if : " << count_if(v2.begin(), v2.end(), Pred1()) << endl; // 단항 조건자
	if(equal(v2.begin(), v2.end(), v1.begin())) cout << "equal(v2.begin(), v2.end(), v1.begin())" << endl;
	if(equal(v2.begin(), v2.end(), v1.begin()+3)) cout << "equal(v2.begin(), v2.end(), v1.begin()+3)" << endl;
	if(equal(v2.begin(), v2.end(), v1.begin(), Pred2)) cout << "equal(v2.begin(), v2.end(), v1.begin(), Pred2)" << endl;
	for_each(v1.begin(), v1.end(), PrintFunctor()); cout << endl;
	for_each(v1.begin(), v1.end(), PrintFunctor(',')); cout << endl;
	for_each(v2.begin(), v2.end(), PrintFunctor('\n')); cout << endl;
	Point pt1(1,1), pt2(2,2);
	Point minpt = min(pt1, pt2, PointPred);
	minpt.print(); cout << endl;
	Point maxpt = max(pt1, pt2, PointPred);
	maxpt.print(); cout << endl;
	return 0;
}
/*
iter-1 : 1, iter : 2, iter+1 : 2
count(v1.begin(), v1.end(), 2) : 2
count_if : 2
equal(v2.begin(), v2.end(), v1.begin()+3)
equal(v2.begin(), v2.end(), v1.begin(), Pred2)
1 2 2 3 4 5 
1,2,2,3,4,5,
3
4
5

[1,1]
[2,2]

원소를 수정하는 알고리즘

  • modifying algorithms은 원소의 값을 변경하거나 목적지 순차열로 원소를 복사하는 알고리즘이다.

copy(b, e, b2)

  • 구간[b,e)의 순차열을 구간[t, t+(e-b))의 순차열로 모두 복사하고, 목적지 끝 반복자를 반환한다.
  • 모든 알고리즘의 기본 동작은 덮어쓰기 이며, 반복자 어댑터(insert_iterator) 등을 이용해 삽입 모드로 동작하게 할 수 있다.

copy_backward(b,e,t)

  • 원소의 복사를 순차열의 마지막 원소부터 복사하고, 목적지 시작 반복자를 반환한다.
  • 구간[b, e)의 모든 원소를 [t-(e-b), t)의 순차열로 마지막 원소부터 복사함

fill(b,e,x)

  • 구간 [b, e)의 원소를 x로 채운다.
  • fill_n(b,n,x) 알고리즘은 구간[b, b+n)의 원소를 x로 채운다.

for_each(b,e,f)

  • 사용자 함수류를 순차열 모든 원소에 적용한다.
    • 출력 매개변수(out parameter)를 사용해 함수를 원소에 적용한다.
      • 그러기 위해 사용자 함수의 매개변수는 반드시 &(레퍼런스)를 사용해야 한다.
  • transfrom(b,e,f)도 비슷하게 작동한다.
    • 함수의 반환값을 사용하여 사용자의 함수를 원소에 적용한다.

generate(b,e,f)

  • 구간[b,e)의 모든 원소를 f()로 채운다.
    • generate()알고리즘과 for_each(), transform() 알고리즘의 큰 차이점은 함수자의 매개변수로 순차열의 원소를 전달받지 않기 때문에 원소가 가지고 있는 원값을 활용할 수 없고, 단순히 일정한 값으로 채운다.
  • generate_n(b,n,f) 알고리즘은 구간[b,b+n)의 모든 원소를 f()로 채운다

swap(a,b)

  • a와 b를 교환한다.
  • iter_swap(p,q)알고리즘은 반복자가 가리키는 *p와 *q를 교환한다.

merge(b,e,b2,e2,t)

  • 정렬된 구간 [b, e)의 순차열과 구간[b2,e2)의 순차열을 [t, t+(e-b)+(e2-b2))의 순차열로 합병 정렬한다.
  • 정렬된 구간에서 동작한다는 점에 주의해야 한다. 특정 기준에 의해 정렬되어 있다면 merge(b,e,b2,e2,t,f) 알고리즘을 이용하면 됨.

replace(b,e,x,x2)

  • 구간 [b,e)의 x인 원소를 x2로 수정한다
  • 조건을 넣고 싶다면 replace_if(b,e,f,x2) 를 사용한다.
    • f는 단항 조건자이다.

replace_copy(b1, e1, b2, x, x2)

  • 조건에 맞는 원소를 수정하여 목적지 순차열로 복사한다.
  • 조건을 넣고 싶다면 replace_copy_if() 알고리즘을 사용한다.

swap_ranges(b,e,b2)

  • 구간 [b,e)의 순차열과 구간[b2, b2+(e-b))의 모든 원소를 교환한다.

transform(b,e,t,f)

  • 구간[b,e)의 반복자가 p라면 f(*p)를 호출하여 반환값을 순차열 [t, t+(e-b))로 저장한다.
  • f는 단항 함수류(함수자, 함수, 함수 포인터)다.
  • transfrom()은 원본의 순차열의 변화 없이 목적지의 순차열로 저장한다는 점이 for_each()와의 차이점이다.
    • 덮어쓰기 모드로 동작한다.
  • transform() 알고리즘은 목적지의 끝 반복자를 반환한다.
  • 조건을 넣고 싶다면 transform(b,e,b2,t,f) 알고리즘 버전을 사용한다.

주의

  • 원소를 수정하는 알고리즘은 모두 디폴트가 덮어쓰기이기 때문에 목적지 순차열은 원본 이상의 원소를 가지고 있어야 한다.
  • 삽입 모드로 동작하려면 insert_iterator를 사용해야 한다. (10장)
struct Accumulation
{
	void operator() (int& n) {
		total += n;
		n = total;
	}
private:
	int total = 0;
};

void print(vector<int> v) {
	for(int n : v) cout << n << " ";
	cout << endl;
}

int main()
{
	vector<int> v1 {1, 3, 5, 7, 9};
	for_each(v1.begin(), v1.end(), Accumulation());
	print(v1);
	swap(v1[4], v1[1]);
	print(v1);
	iter_swap(v1.begin(), v1.end()-1);
	print(v1);
	vector<int> v2 = {1, 3, 5};
	vector<int> v3 (3, 2);
	vector<int> v4(10, 0); // 빈 컨테이너면 에러
	merge(v2.begin(), v2.end(), v3.begin(), v3.end(), v4.begin());
	print(v4);
	replace(v4.begin(), v4.end(), 0, 4);
	print(v4);
	return 0;
}
/*
1 4 9 16 25 
1 25 9 16 4 
4 25 9 16 1 
1 2 2 2 3 5 0 0 0 0 
1 2 2 2 3 5 4 4 4 4 

제거 알고리즘

  • removing algorithms은 ‘원소를 수정하는 알고리즘’의 특수한 형태이다.
  • 원소를 실제로 제거하지 않고 논리적으로 제거(다음 원소로 덮어쓰기)하기 때문에 순차열의 size를 실제로 변경하지 않는다.
  • 실제 size를 변경하려면 컨테이너의 erase() 멤버 함수를 이용하면 된다

remove(b,e,x)

  • 구간 [b,e)의 순차열에서 x인 원소가 남지 않게 함.
  • 이 때 remove는 실제 원소를 제거하지 않고 뒤의 원소들을 앞으로 복사시킴.
  • size에는 변동이 없으며 remove() 후의 순차열은 [b,p)가 된다.
    • p는 remove(b, e, x)의 리턴값
  • 조건에 따라 원소를 제거하고자 한다면 remove_if() 알고리즘을 사용한다.

remove_copy(b,e,t,x)

  • 구간 [b,e)에서 *p==x인 원소를 제외한 원소를 순차열[t,p)에 복사한다.
  • 원소 복사는 당연히 덮어쓰기 모드로 동작한다.
  • remove_copy_if() 는 조건자 버전

unique(b,e)

  • 구간[b,e)의 순차열을 인접한 중복 원소가 남지 않게 한다.
  • 수행 후 순차열은 [b,p)가 된다.
  • 주의할 점은 인접한 중복 원소를 제거하기 대문에 결과에서 인접하지 않는 중복 원소는 남게 된다는 사실이다.
  • 정렬 후 알고리즘을 수행하면 깔끔하게 된다. 또한 중복 원소를 논리적으로 제거한다.
  • 조건자를 이용한다면 unique(b,e,f)를 이용하면 된다.
  • 중복원소 자리를 뒷 원소가 복사이동하는 방식이라 size에 변함이없다.
int main()
{
	vector<int> v1{1, 3, 5, 7};
	cout << "capacity : " << v1.capacity() << " , size : " << v1.size() << endl;
	auto iter_end = remove(v1.begin(), v1.end(), 1);
	cout << "capacity : " << v1.capacity() << " , size : " << v1.size() << endl;
	print(v1);
	cout << "iter - v1.begin() : " << iter_end - v1.begin() << endl;
	iter_end = unique(v1.begin(), v1.end());
	print(v1);
	cout << "iter - v1.begin() : " << iter_end - v1.begin() << endl;
	for(int i=0; i<4; ++i) v1.push_back(5);
	print(v1);
	unique(v1.begin(), v1.end());
	print(v1);
	v1.push_back(3);
	iter_end = unique(v1.begin(), v1.end());
	print(v1);
	cout << "iter - v1.begin() : " << iter_end - v1.begin() << endl;
	v1.erase(iter_end, v1.end());
	print(v1);
	return 0;
}
/*
capacity : 4 , size : 4
capacity : 4 , size : 4
size : 4  [3, 5, 7, 7]
iter - v1.begin() : 3
size : 4  [3, 5, 7, 7]
iter - v1.begin() : 3
size : 8  [3, 5, 7, 7, 5, 5, 5, 5]
size : 8  [3, 5, 7, 5, 5, 5, 5, 5]
size : 9  [3, 5, 7, 5, 3, 5, 5, 5, 3]
iter - v1.begin() : 5
size : 5  [3, 5, 7, 5, 3]

변경 알고리즘

mutating algorithms은 순차열의 원소를 서로 교환하거나 이동하여 순차열 원소의 ‘순서’를 변경하는 알고리즘이다.

next_permutation(b,e)

  • 구간 [b,e)의 순차열을 다음 순열(사전순)의 순차열이 되게 한다. 마지막 순열이라면 false를 반환하며 아니면 true를 반환한다.
  • 원소의 순서를 순열(permutation)처럼 변경할 때 next_permutation()과 prev_permutation() 알고리즘을 사용한다.
  • 사용자 조건으로 순열을 생성하려면 next_permutation(b,e,f)
  • prev_permutation은 next_permutation과 비슷하며 순차열의 이전 순열을 만들어내고 첫 순열일 때 false를 반환한다.

partition(b,e,f)

  • [b,e)의 반복자가 p일 때 f(*p)가 참인 원소는 [b,p)의 순차열에 거짓인 원소는 [p,e)의 순차열로 분류한다.
  • 원소의 상대적인 순서는 변경하지 않게 하려면 stable_partition() 알고리즘을 사용한다. 성능은 조금 떨어진다.

reverse(b,e)

  • 구간 [b,e)의 순차열을 역순으로 뒤집는다.
  • 뒤집힌 순차열을 목적지 순차열로 복사하고자 한다면 reverse_copy(b,e,t). 덮어쓰기 모드이다

rotate(b,m,e)

  • 첫 원소와 마지막 원소가 연결된 것처럼 왼쪽으로 m-e만큼씩 회전한다.
  • 순차열을 회전하여 목적지 순차열에 복사하려면 rotate_copy(b,m,e,t) 를 사용한다.
int main()
{
	vector<int> v {5, 7, 9, 3, 1, 8, 6};
	print(v);
	auto iter = partition(v.begin()+1, v.end(), Pred(5));
	if(*v.begin() > *iter) iter_swap(v.begin(), iter);
	print(v);
	reverse(v.begin(), v.end());
	print(v);
	rotate(v.begin(), v.begin()+3, v.end());
	print(v);
	return 0;
}
/*
size : 7  [5, 7, 9, 3, 1, 8, 6]
size : 7  [5, 1, 3, 9, 7, 8, 6]
size : 7  [6, 8, 7, 9, 3, 1, 5]
size : 7  [9, 3, 1, 5, 6, 8, 7]

정렬 알고리즘

힙관련

  • make_heap(), push_heap(), pop_heap(), sort_heap()이 있다.

make_heap()

  • 순차열을 힙으로 만든다.
  • default는 가장 큰 값부터 작은 순으로 나열된다.

push_heap(b,e)

  • 구간[b,e)의 힙에 원소를 추가하는 알고리즘이다.
  • 일반적으로 멤버 함수 push_back()과 함께 사용된다.

pop_heap(b,e)

  • 구간의 첫 원소(가장 큰 원소)를 가장 끝 원소와 교환한 후 힙이 유지되게 하면서 제거한다.

sort_heap(b, e)

  • 힙을 정렬. 디폴트는 오름차순으로 정렬

조건 사용

  • make_heap(b,e,f), push_heap(b,e,f), pop_heap(b,e,f), sort_heap(b,e,f) 모두 조건자 버전 힙 알고리즘으로 f를 조건자로 사용하여 힙 연산을 수행한다.

sort(b,e), stable_sort(b,e), partial_sort(b,e)

  • 순차열의 원소를 정렬.
  • sort() 는 퀵정렬을 기반으로 하며, stable_sort()는 머지정렬을 기반으로, partial_sort()는 힙정렬을 기반으로 한다.
  • sort() 알고리즘의 경우 평균적으로 O(nlogn), 최악의 경우 O($n^2$) 복잡도를 가지며,
  • stable_sort() 는 메모리만 넉넉하다면 O(nlogn), 그렇지 않다면(nlognlogn)의 복잡도를 가진다.
    • 머지정렬을 기반으로 하므로 같은 원소의 정렬에서 원소의 상대적인 순서가 유지된다.
  • partial_sort()는 힙정렬을 기반으로 하므로 언제든지 O(nlogn)의 복잡도를 보장하지만 퀵정렬보다는 성능이 떨어진다.
  • 조건자를 사용하려면 2항 조건자를 파라미터에 추가시키면 된다.
int main()
{
	vector<int> v {1, 3, 2, 5, 4};
	print(v);
	make_heap(v.begin(), v.end());
	print(v);
	v.push_back(7);
	print(v);
	push_heap(v.begin(), v.end());
	print(v);
	pop_heap(v.begin()+2, v.end());
	print(v);
	sort_heap(v.begin(), v.end());
	print(v);

	vector<int> v1 {1, 3, 2, 5, 4};
	vector<int> v2(v1), v3(v1);
	sort(v1.begin(), v1.end());
	print(v1);
	print(v2);
	stable_sort(v2.begin(), v2.end());
	print(v2);
	print(v3);
	partial_sort(v3.begin(), v3.begin()+2, v3.end());
	print(v3);
	return 0;
}
/*
size : 5  [1, 3, 2, 5, 4]
size : 5  [5, 4, 2, 3, 1]
size : 6  [5, 4, 2, 3, 1, 7]
size : 6  [7, 4, 5, 3, 1, 2]
size : 6  [7, 4, 3, 2, 1, 5]
size : 6  [1, 2, 3, 4, 5, 7]
size : 5  [1, 2, 3, 4, 5]
size : 5  [1, 3, 2, 5, 4]
size : 5  [1, 2, 3, 4, 5]
size : 5  [1, 3, 2, 5, 4]
size : 5  [1, 2, 3, 5, 4]

정렬된 범위 알고리즘

sorted range algorithms은 정렬된 구간에서만 동작하는 알고리즘이다.
즉, 반드시 정렬돼 있어야 한다.
또한, 같음을 비교할 때 연관 컨테이너처럼 두 원소 a, b에 대해 equality 연산을 하지 않고 !(a<b) && !(b<a) 연산(equivalence)를 함.

binary_search(b,e,x)

  • 구간의 순차열에서 x와 같은 원소가 있으면 true를 반환, 없으면 false를 반환.
  • 이진 탐색한다.

includes(b,e,b2,e2)

  • [b2,e2)의 원소가 구간[b,e)의 원소에 포함되면(부분 집합) ture, 아니면 false를 반환한다.
  • 한 순차열의 다른 순차열의 부분 집합인지 판단하려면 includes() 알고리즘을 사용한다

lower_bound(), upper_bound()

  • 연관 컨테이너의 멤버 함수 lower_bound(), upper_bound() 와 비슷하다
  • 조건자 버전도 있다

equal_range()

  • 연관 컨테이너의 멤버 함수 equal_range() 와 비슷
  • 조건자 버전도 있음

merge()

  • 연관 컨테이너의 멤버 함수 merge() 와 비슷
  • 조건자 버전도 있음

inplace_(b,m,e)

  • 하나의 순차열이 두 구간으로 정렬돼 있다면 하나의 구간으로 정렬할 수 있음.
  • 알고리즘은 정렬된[b,m)의 순차열과 정렬된 [m,e) 순차열을 정렬된 [b,e) 순차열로 합병한다.
  • 조건자 버전도 있음

set_union(b,e,b2,e2,t)

  • 두 구간의 순차열을 합집합하여 목적지 순차열 [t,p)에 저장한다.
  • 조건자 버전도 있음

set_intersection(b,e,b2,e2,t)

  • 교집합

set_difference()

  • 차집합

수치 알고리즘

수치관련 원소를 다루는 알고리즘.
numeric algorithms은 다른 알고리즘과 달리 헤더에 정의된다.

accumulate(b,e,x)

  • x를 초깃값으로 한 구간 [b,e)의 모든 원소 합을 반환한다.
  • 조건을 넣으려면 accumulate(b,e,x,f)
    • f는 이항

inner_product(b,e,b2,x)

  • x를 초깃값으로 구간 [b,e)의 원소와 구간 [b2, b2+e-b)의 두 순차열의 내적(모든 원소의 곱의 합)을 반환함.
  • 함수류 버전은 inner_product(b,e,b2,x,f1,f2) 이다.
    • 각 구간의 원소에 대해 a와 b 각각의 f2 연산 결과를 f1 연산함.

adjacent_difference(b,e,t)

  • 구간[b,e)의 반복자가 p일 때 (*p - *(p-1)) 연산을 목적지 순차열 [t,p)에 저장한다.
  • 순차열에서 원소 간의 차를 구하려면 adjacent_difference() 알고리즘을 사용한다

partial_sum(b,e,t)

  • 구간 [b,e)의 현재 원소까지의 합(누적)을 목적지 순차열 [t,p)에 저장한다. 순차열에서 현재 원소까지의 합을 구하고자 한다면 partial_sum() 을 사용한다.
int main()
{
	vector<int> v1 {1, 3, 5, 7};
	vector<int> v2 {2, 4, 6, 8};
	cout << "accumulate : " << accumulate(v1.begin(), v1.end(), 0) << endl;
	print(v1);
	print(v2);
	cout << "inner product + 1: " << inner_product(v1.begin(), v1.end(), v2.begin(), 1) << endl;
	vector<int> v3(4,0);
	adjacent_difference(v1.begin(), v1.end(), v3.begin());
	print(v3);
	partial_sum(v2.begin(), v2.end(), v3.begin());
	print(v3);
	return 0;
}
/*
accumulate : 16
size : 4  [1, 3, 5, 7]
size : 4  [2, 4, 6, 8]
inner product + 1: 101
size : 4  [1, 2, 2, 2]
size : 4  [2, 6, 12, 20]

Leave a comment