힙정렬
- 시간복잡도가 O(nlogn)으로 퀵정렬, 병합정렬과 같은 시간복잡도를 가진 정렬 알고리즘
- 병합정렬과는 다르게 추가적인 메모리 필요 X
- 항상 O(nlogn)의 성능을 보여주기 때문에 최악의 경우 O(n^2)의 성능을 보이는
퀵정렬보다 안정적인 성능을 보인다. - 이론적으로는 퀵 정렬 병합 정렬보다 우위에 있지만, 단순히 속도만 가지고 본다면 퀵 정렬이
평균적으로 더 빠르기 때문에 힙 정렬이 많이 사용되지는 않는다. - 배열을 최대힙 or 최소힙으로 만들어 root에 있는 값을 정렬하는 식으로 구현한다.
힙(Heap)
- 완전이진트리 형태의 자료구조
- 힙은 최소힙과 최대힙 두가지로 나뉜다.
- 힙은 일반적인 배열로 표현할 수 있다.
최대 힙 : "부모 노드 > 자식 노드"를 모든 노드에서 만족하는 완전이진트리
최소 힙 : "부모 노드 < 자식 노드"를 모든 노드에서 만족하는 완전이진트리
완전이진트리
- 노드를 삽입할 때 왼쪽부터 차례대로 추가하는 이진트리 ( 모든 노드의 차수가 2 이하로 구성된 트리 )
완전이진트리(각 노드의 왼쪽부터 채워진 이진트리) | 그냥 이진트리 |
힙을 일반적인 배열로 표현
a = {-1,3,4,5,1,7,2,8}; 이라는 배열이 있다고 가정하자.
a[1]을 루트노드로 잡고 힙트리를 완성하면 밑에 그림과 같다.
i = 현재 노드의 인덱스
☞ 부모노드 = i / 2 (i > 0)
☞ 왼쪽 자식 노드 = i * 2
☞ 오른쪽 자식 노드 = i * 2 + 1
배열을 최대힙(or 최소힙)으로 만들어주는 힙 생성 알고리즘 ( heapify )
- 힙 구조(최대힙 or 최소힙 구조)로 만들어 주기 위해 하나의 노드에 대해서 수행하는 알고리즘
- 특정 노드의 두 자식 중에 더 큰 자식 <-> 자기 자신 스위칭
( 이 과정을 모든 노드에 대해서 반복 )
5 때문에 최대 힙 구조 X | heapify() 후 최대 힙 구조 O |
- 하나의 노드에 대해 한 번 heapify()를 수행하는데 걸리는 시간 : O(logN)
- 전체 노드가 힙 구조를 다 만족하도록 만드는데 걸리는 시간 ( 제일 왼쪽 아래 노드부터 heapify() 시작 )
( == 트리에 있는 모든 노드에 대해 heapify()를 수행하는데 걸리는 시간 )
=> O(N * logN) ( 노드 개수 * 특정 노드에 대해 heapify() 1번 할 때 걸리는 시간 )
- 실제로 전체를 힙 구조를 만족하도록 만드는데 걸리는 시간
Ex) 이진탐색트리의 높이 : logN / 총 노드 갯수 : N
- 실제로는 노드 갯수의 반만큼만 heapifiy()를 하면 되므로 ( 리프 노드들은 자식이 없기 때문에 할 필요 x )
=> N/2 * logN 이 된다.
=> N이 무한히 커진다면 N/2 > logN라서 logN은 상수급으로 작아진다.
=> 그러므로 시간복잡도가 O(N * logN)이긴 하지만 logN이 상수급으로 작아지기 때문에 거의 O(N)에
가까운 속도를 낼 수 있다.
힙정렬의 원리
- 힙을 이용해 정렬을 하기 위해서는 이 힙을 최소힙이나 최대힙으로 만들어야 한다.
힙이 최소힙 or 최대힙이 되었을 때 root는 항상 힙에서 최대값 or 최소값이 나오게 되어 있다.
이 때 root를 힙의 마지막 노드와 교체한 뒤 힙의 마지막을 제외한 후 다시 최소힙이나 최대힙을
만들어 준 후 root를 마지막 노드와 교체하는 방식으로 계속 반복한다.
힙 정렬 순서
1) 정렬되지 않은 배열 생성
2) 제일 처음에 이 배열을 최소힙 or 최대힙으로 구성 ( 모든 노드에 대해 힙 생성 알고리즘 heapify() 사용 )
3) root <-> 마지막 노드 교체
4) 마지막 노드를 제외한 상태로 다시 최소힙 or 최대힙 구성 ( root노드에 대해 다운힙 : O(logN) )
5) 3 ~ 4번 반복
=> 최종적으로 힙정렬하는데 드는 시간
=> 처음 최대힙으로 만드는데 걸린 시간 + 스위칭한 후 root에 대해 다운힙하는데 걸리는 시간
=> O(N) + O(N * logN)
=> O(N * logN)
힙 정렬 구현 코드
#include<stdio.h>
#include<iostream>
using namespace std;
int n = 7;
int heap[8] = {-100,1,9,5,4,7,2,8};
void heapify(int i){
int parent = i;
int child = i * 2;
if(heap[child] < heap[child+1]) child++;
if(heap[parent] < heap[child]){
swap(heap[parent],heap[child]);
if(child <= n / 2){
heapify(child);
}
}
}
void heapSort(int i){
int root = 1;
int child = 2;
swap(heap[root], heap[i]);
//마지막 노드를 제외하고 최대힙으로 구성
do{
child = root * 2;
if((heap[child] < heap[child + 1]) && (child < i - 1)){
child++;
}
if(heap[root] < heap[child] && child < i){
swap(heap[root],heap[child]);
}
root = child;
}while(child < i);
}
int main(){
//처음에 최대 힙으로 만들기
for(int i = n / 2; i >= 1; i--){
heapify(i);
}
//힙 정렬 수행
for(int i = n; i > 0; i--){
heapSort(i);
// printf("%d : ",i);
// for(int j = 1; j <= n; j++){
// printf("%d ",heap[j]);
// }
// printf("\n");
}
}
'알고리즘 개념' 카테고리의 다른 글
스택(Stack), 큐(Queue) (0) | 2022.02.15 |
---|---|
계수 정렬 ( Counting Sort ) (0) | 2022.02.15 |
C++ STL sort() 함수 다루기 (0) | 2022.02.10 |
병합 정렬 ( Merge sort ) (0) | 2022.02.09 |
버블 정렬 ( Bubble sort ) (0) | 2022.02.09 |