Posts 알고리즘 - map 자료구조를 정렬하기(map 정렬, 반복자 어댑터)
Post
Cancel

알고리즘 - map 자료구조를 정렬하기(map 정렬, 반복자 어댑터)


C++에서 STL 라이브러리로 map을 사용할 때, map의 자료들을 마음대로 정렬하고 싶을 경우가 있습니다.

(기본적으로 map은 key 값을 기준으로 자동 오름차순 정렬됩니다.)

map은 보통 아래와 같이 선언합니다.

1
2
3
#include <map>
...
map<int, int> maps; // first가 key, second가 value

이 때 map의 key 값 혹은 value 값으로 오름차순이나 내림차순 정렬을 하고 싶으면,

먼저 map의 pair 데이터들을 벡터에 옮겨 담습니다.

1
2
3
4
5
6
7
8
9
#include <map>
#include <vector>
#include <algorithm>
...
map<int, int> maps;
vector<pair<int, int>> vec;
...

copy(maps.begin(), maps.end(), back_inserter(vec));

여기서 사용한 것이 copy 함수와 back_inserter 인데, 잠시 이것에 대해 알아보겠습니다.


copy 함수


  • 둘 다 ‘algorithm‘에 정의되어 있음

  • 헤더파일에는 다음과 같이 정의되어 있습니다.

1
2
3
4
5
6
7
template<class InputIt, class OutputIt>
OutputIt copy(InputIt first, InputIt last, OutputIt d_first){
    while (first != last) {
        *d_first++ = *first++;
    }
    return d_first;
}

  • copy 는 반복자를 사용할 수 있는 모든 컨테이너들에 대하여 사용 가능한데, 다음과 같이 사용합니다..
1
2
3
4
5
//1. 벡터의 경우, source 벡터에서 target 벡터로 복사할 때 아래와 같이 사용
//source의 처음부터 끝까지를, target의 처음에 복사한다는 의미
using namespace std;
...
copy(srcVec().begin(), srcVec().end(), targetVec().begin());
1
2
3
4
5
//2. map을 map에 복사할 경우, 2가지 방법 존재
using namespace std;
...
targetMap.insert(srcMap.begin(), srcMap.end()); // 방법 1
copy(srcMap.begin(), srcMap.end(), inserter(targetMap.begin(), targetMap.end()); // 방법 2

벡터와 map은 copy 방법이 약간 다릅니다. 즉, copy 함수의 맨 마지막 인자에 오는 것이 좀 다른데요.

이것은 벡터나 map에 원소를 추가할 때 사용하는 방식이 달라서 그렇습니다.

  • vector -> push_back 사용
  • map -> insert 사용

그렇기 때문에 map의 경우 따로 구분해서 알아둘 필요가 있습니다. 두 개의 map 간의 복사를 할 때 벡터와 같은 방법을 사용하면 오류가 나게 됩니다.

그러면 map 복사시에 쓰였던 inserter는 무슨 역할을 하는지 알아보겠습니다.

(방법 1과 방법 2가 동일하므로, 무슨 역할인지 짐작하신 분도 계실 겁니다.)


inserter, back_inserter, front_inserter

이들은 모두 algorithm 헤더에 정의되어 있고, 반복자 어댑터라고 부릅니다.

컨테이너(리스트, 벡터, 맵, …)들은 iterator를 사용할 때, 대상이 되는 컨테이너들에 공간(capacity)이 충분하기를 원합니다.

하지만 예기치 않게 컨테이너에 얼만큼의 자료가 들어올 지 모르는 상황에서 reserve로 capacity를 마련해 주기는 힘드므로, 이 반복자 어댑터들을 사용하게 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using namespace std;
map<int, int> srcMap;
map<int, int> targetMap;
list<int> srcList;
list<int> targetList;
vec<int> srcVec;
vec<int> targetVec;

// srcMap의 원소를 차례대로 targetMap 의 시작부터 insert를 호출하여 넣어 준다.
// 즉, targetMap.insert(srcMap.begin()), targetMap.insert(srcMap.begin()+1), ...
copy(srcMap.begin(), srcMap.end(), inserter(targetMap.begin(), targetMap.end());

// srcList의 원소를 차례대로 targetList의 시작부터 push_back을 호출하여 넣어 준다.  
copy(srcList.begin(), srcList.end(), back_inserter(targetList.begin(), targetList.end());
     
// back_inserter 는 내부적으로 push_back을 호출하므로, 벡터에도 활용 가능하다.
// 단, 벡터는 back_inserter 안에 벡터 이름만 들어가야 한다.
// 아래와 같이 호출할 경우, targetVec 맨 끝에서부터 srcVec 내용이 append 되는 효과를 얻는다.
copy(srcVec.begin(), srcVec.end(), back_inserter(targetVec));

이렇게 copy 함수 맨 마지막 인자에 사용할 수 있는 반복자 어댑터에 대해 살펴 보았습니다.

이제 다시 map을 벡터에 복사하는 상황으로 돌아와 보겠습니다.

1
2
3
4
5
6
7
8
9
#include <map>
#include <vector>
#include <algorithm>
...
map<int, int> maps;
vector<pair<int, int>> vec;
...

copy(maps.begin(), maps.end(), back_inserter(vec));

이제 어떻게 copy가 동작했는지 파악이 될 것입니다.

back_inserter 를 사용하여 내부에서 push_back이 일어나도록 하므로, vec 벡터에 maps의 pair 들이 차례대로 push_back 되어 복사되고 있습니다.

즉, map의 원소들을 벡터에 넣어줄 때는 위처럼 반복자 어댑터를 사용해야 오류가 나지 않게 됩니다.


map의 모든 데이터를 벡터에 담았으면, 이제 그 벡터를 정렬하면 됩니다.

이 때, map의 value 값으로 정렬하고 싶거나, key 혹은 value 에 따른 내림차순 정렬을 하고 싶거나 하는 등 자신의 목적에 맞게 compare 함수를 만들어 준 뒤 정렬하면 됩니다.

1
2
...
sort(vec.begin(), vec.end(), compare);

그러면 map의 데이터들을 정렬한 것과 같은 결과를 보일 수 있게 됩니다.

[알고리즘 트레이닝] 2장 - 프로그래밍 기법 : 재귀적 알고리즘

[책 소개] 알고리즘 트레이닝 - 프로그래밍 대회 입문 가이드