힙 정렬(Heap Sort)
병합 정렬, 퀵 정렬만큼 빠른 정렬 알고리즘
가장 큰 원소를 찾는 데 최적화된 형태의 이진 트리
힙의 가장 중요한 원칙
- 부모 노드가 가진 원소는 항상 자식 노드가 가진 원소의 크기보다 크다.
- 트리에서 가장 큰 원소는 항상 트리의 루트에 존재한다.
힙의 모양 규칙
- 마지막 레벨을 제외한 모든 레벨에 노드가 꽉 차 있어야 한다.
- 마지막 레벨에 노드가 있을 때는 항상 가장 왼쪽부터 순서대로 채워져 있어야 한다.
힙 트리 구조(Heap Tree Structure)를 이용하는 정렬 방식
- 크기가 n인 완전 이진트리
- 힙(Heap) 이란 최소값이나 최대값을 빠르게 찾아내기 위해 완전 이진트리를 기반으로 하는 자료구조
- 최댓값이란 부모 노드의 키 값이 자식 노드의 키 값보다 항상 큰 힙
- 최솟값이란 부모 노드의 키 값이 자식 노드의 키 값보다 항상 작은 힙
- 키 값의 대소 관계는 오직 부모 노드와 자식 노드 간에만 성립하며, 형제 사이에는 대소 관계가 정해지지 않는다.
힙을 사용할 경우 새 원소를 추가하는 연산과 가장 큰 원소를 꺼내는 연산을 모두 O(logN) 시간에 수행할 수 있다.
코드 설계
- 힙 구조의 규칙
- A[i]에 대응되는 노드의 왼쪽 자손은 A[2 * i + 1]에 대응된다.
- A[i]에 대응되는 노드의 오른쪽 자손은 A[2 * i + 2]에 대응된다.
- A[i]에 대응되는 노드의 부모는 A[(i-1)/2]에 대응된다.(나눗셈 결과는 내림한다.)
- 정렬 과정
- 마지막 노드의 부모 노드부터 시작하여 root 노드순으로 검색한다.
- 정렬의 과정은 최댓값을 검증하여 정상적인 힙 구조인지 확인, 아닐 경우 child와 parent를 변경
코드 구현
private void swap(int[] A, int pre, int post) {
System.out.println("swap: \tA[" + pre + "]=" + A[pre] + " <> A[" + post + "]=" + A[post]);
int tmp = A[pre];
A[pre] = A[post];
A[post] = tmp;
}
/**
* 힙 정렬
* @param A
*/
private void heapSort(int[] A) {
int aLength = A.length - 1; // 배열의 실 길이
int parentIdx = aLength / 2;
// 마지막 노드의 부모 노드부터 시작하여 root 노드 방향으로 거슬러 올라감
for (int i = parentIdx; i >= 0; i--) maxHeapify(A, A.length, i);
for(int i = aLength ; i > 0 ; i--) { // 원소가 하나 남을 때까지 반복
swap(A, 0, i); // 배열의 마지막 요소 값을 루트로 변경
maxHeapify(A, i, 0);
}
}
/**
* 배열 A를 힙으로 만드는 메서드
* 루트에 있는 가장 큰 값을 빼내어 마지막 요소와 바꾸고,
* 배열의 나머지 부분을 다시 힙으로 만드는 과정을 반복하여 정렬을 수행
* @param A
* @param length
* @param i
*/
private void maxHeapify(int[] A, int length, int i) {
int parent = i; // parent index
int leftChild = (i * 2) + 1; // leftChild index
int rightChild = (i * 2) + 2; // rightChild index
// leftChild가 parent보다 클 경우
if(leftChild < length && A[parent] < A[leftChild]) {
parent = leftChild;
}
// rightChild가 parent보다 클 경우
if(rightChild < length && A[parent] < A[rightChild]) {
parent = rightChild;
}
if(i != parent) {
swap(A, parent, i);
maxHeapify(A, length, parent-1);
}
}
- 과정
01
/\
04 05
/\ /\
07 02 06 03
/
08
A[7]=8 < swap > A[3]=7
01
/\
04 05
/\ /\
08 02 06 03
/
07
A[5]=6 < swap > A[2]=5
01
/\
04 06
/\ /\
08 02 05 03
/
07
A[3]=8 < swap > A[1]=4
01
/\
08 06
/\ /\
04 02 05 03
/
07
A[1]=8 < swap > A[0]=1
08
/\
01 06
/\ /\
04 02 05 03
/
07
A[0]=8 < swap > A[7]=7
07
/\
01 06
/\ /\
04 02 05 03
/
08
A[0]=7 < swap > A[6]=3
03
/\
01 06
/\ /\
04 02 05 07
/
08
A[2]=6 < swap > A[0]=3
06
/\
01 03
/\ /\
04 02 05 07
/
08
A[3]=4 < swap > A[1]=1
06
/\
04 03
/\ /\
01 02 05 07
/
08
A[5]=5 < swap > A[2]=3
06
/\
04 05
/\ /\
01 02 03 07
/
08
A[0]=6 < swap > A[5]=3
03
/\
04 05
/\ /\
01 02 06 07
/
08
A[2]=5 < swap > A[0]=3
05
/\
04 03
/\ /\
01 02 06 07
/
08
A[0]=5 < swap > A[4]=2
02
/\
04 03
/\ /\
01 05 06 07
/
08
A[1]=4 < swap > A[0]=2
04
/\
02 03
/\ /\
01 05 06 07
/
08
A[0]=4 < swap > A[3]=1
01
/\
02 03
/\ /\
04 05 06 07
/
08
A[2]=3 < swap > A[0]=1
03
/\
02 01
/\ /\
04 05 06 07
/
08
A[0]=3 < swap > A[2]=1
01
/\
02 03
/\ /\
04 05 06 07
/
08
A[1]=2 < swap > A[0]=1
02
/\
01 03
/\ /\
04 05 06 07
/
08
A[0]=2 < swap > A[1]=1
01
/\
02 03
/\ /\
04 05 06 07
/
08
분석
- 힙 정렬은 선택 정렬을 응용한 알고리즘
- 첫 요소를 꺼내는 것만으로 가장 큰 값이 구해지므로 첫 요소를 꺼낸 다음 나머지 요소를 다시 힙으로 만들어야 그 다음에 꺼낼 요소도 가장 큰 값을 유지한다.
- 따라서 단순 선택 정렬에서 가장 큰 요소를 선택할 때의 시간 복잡도 O(n)의 값을 한 번에 선택할 수 있어 O(1)로 줄일 수 있다.
- 복잡도 분석
- 힙으로 만드는 작업의 시간 복잡도 O(logn)
- 힙으로 만드는 작업을 요소의 개수 만큼 반복하므로 O(n logn)
'알고리즘&자료구조 > basic' 카테고리의 다른 글
[자료구조] 스택(Stack) (0) | 2020.05.10 |
---|---|
[알고리즘] 계수 정렬(Counting Sort) (0) | 2020.05.10 |
[알고리즘] 퀵 정렬(Quick Sort) (0) | 2020.05.04 |
[알고리즘] 병합 정렬(Merge Sort) (0) | 2020.05.04 |
[알고리즘] 삽입정렬(Insertion Sort) (0) | 2020.05.04 |