본문으로 바로가기

STL) Heap 과 관련된 연산들

category C++/STL 2019. 2. 18. 14:31
이번에 STL에 대한 공부를 하며 작성하기로 했다.
모든 STL에 대한 내용은 

CppCon 2018: Jonathan Boccara “105 STL Algorithms in Less Than an Hour”

를 참조했다. ( 링크 : https://www.youtube.com/watch?v=2olsGf6JIkU&t=964s )

오늘은 STL의 make_heap, sort_heap, push_heap 등 heap 관련 연산을 작성하겠다.
그전에 힙과, 힙정렬에 대해 간단히 알아두면 좋다.

링크 : https://ko.wikipedia.org/wiki/%ED%9E%99_(%EC%9E%90%EB%A3%8C_%EA%B5%AC%EC%A1%B0) 

( 힙에 대한 위키피디아의 정보 )

 

<make_heap>
make_heap 은 [first, end) 구역에 있는 모든 원소를 힙으로 만들어 냅니다.
인자로는 first, end, functor = less<> 를 받습니다. make_heap 은 기본적으로 최대 힙( max heap ) 을 구성합니다.

 

1
2
3
4
5
6
int arr[10= { 1,2,3,4,5,6,7,8,9,10 };
std::make_heap(arr, arr + 10);    // 최대 힙
std::make_heap(arr, arr + 10, [](int _t1, int _t2) ->bool {
    return (_t1 < _t2);
});    // 최대 힙, 람다도 사용 가능
std::make_heap(arr, arr + 10, greater<int>());    // 최소 힙, greater나 less 도 사용가능 

임의 접근 반복자가 있다면, 모든 컨테이너에 대해서 heap을 만들어 낼 수 있습니다.

 

1
2
3
4
5
6
7
vector<int> nvt =  { 1,2,3,4,5,6,7,8,9,10 };
array<int10> narr = { 1,2,3,4,5,6,7,8,9,10 };
list<int> nlist = { 1,2,3,4,5,6,7,8,9,0 };

std::make_heap(nvt.begin(), nvt.end());    //ok , vector는 임의 접근 반복자 입니다
std::make_heap(narr.begin(), narr.end());    //ok, array 도 임의 접근 반복자 입니다.
std::make_heap(nlist.begin(), nlist.end());    // error, list는 양방향 반복자 입니다.

make_heap은 log(N)의 복잡도를 가지며 최대 3N 까지 비교합니다.

 

<push_heap>

push_heap은 이미 만들어진 힙 컨테이너에 힙을 다시 추가하는 함수입니다.

1
2
3
4
vector<int> nvt = { 1,2,3,4,5,6,7,8,9,10 };
make_heap(nvt.begin(), nvt.end());    
nvt.push_back(100);
push_heap(nvt.begin(), nvt.end()); // 최대 힙은 100

이게 왜 이렇게 작동하냐에 의문점이 들 수도 있습니다.

예로부터 힙을 생성하고, 새로운 값을 넣을땐 end 번째의 노드에 새로운 값을 추가하고, 그것을 부모노드와 비교하며 재 정렬 해주었습니다.

위 또한 100의 값을 마지막에 넣고, push_heap으로 맨 마지막 노드인 100을 재 정렬 해나가는 과정이라고 보시면 됩니다.

 

push_heap은 O(log N)의 복잡도를 가지며, 최대 log N 번의 비교를 수행합니다.

 

<pop_heap>

pop_heap은 만들어진 힙 컨테이너에 루트노드를 빼내는 함수입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main() {
    vector<int> nvt =  { 1,2,3,4,5,6,7,8,9,10 };
    std::make_heap(nvt.begin(), nvt.end());
    for (auto& i : nvt) {
        cout << i << endl;
    }// 10 9 7 8 5 6 3 1 4 2
    std::pop_heap(nvt.begin(), nvt.end());
    for (auto& i : nvt) {
        cout << i << endl;
    }    // 9 8 7 4 5 6 3 2 1 2 10

    int i = nvt.back();    // 힙의 최댓값(10) 을 가져옵니다.
    nvt.pop_back();    // 최댓값을 삭제합니다.
}

위 같은 경우, nvt는 max heap 으로 설정되었기때문에 루트 노드인 최댓값 10을 가져옵니다.

pop_heap은 호출될 시, 부모노드를 맨 마지막 자식노드인 end와 교환하고 부모노드를 재정렬 합니다.

그리고 pop_back() 을 통해 컨테이너 내에서 마지막 노드를 빼내야 합니다.

 

pop_heap은 O(log N)의 복잡도를 가지며, pop_heap은 최대 2 log N 의 연산을 수행합니다.

 

<sort_heap>

sort_heap은 힙을 재정렬 합니다.

1
2
3
4
vector<int> nvt =  { 1,2,3,4,5,6,7,8,9,10 };
std::make_heap(nvt.begin(), nvt.end());
random_shuffle(nvt.begin(), nvt.end());
std::sort_heap(nvt.begin(), nvt.end());

위 같이, 섞여버린 힙을 다시 sort_heap을 통해 재정렬합니다.

 

sort_heap은 최대 O(N log N)의 복잡도를 가지며, N lon N 만큼 연산을 수행합니다.

 

이러한 heap 연산 알고리듬을 사용하여 정렬을 수행하려면, 

위의 make_heap(first, last)과 sort_heap(first, last)을 차례로 사용하거나, partial_sort(first, last, last)를 사용하면 됩니다.)