힙 정렬(Heap Sort)

한유진·2021년 10월 24일
0

알고리즘

목록 보기
5/8

정의


힙 : 최소값 또는 최대값을 빠르게 찾아내기 위한 완전이진트리 형태로 만들어진 자료구조
노드는 항상 우선순위가 높은 노드 == 최대값과 최소값을 빠르게 찾을 수 있다. (시간복잡도 : O(1))

  • 최소 힙 : 각 노드의 값은 자기 자식의 값보다 작다
  • 최대 힙 : 각 노드의 값은 자기 자식의 값보다 크다

-힙 구조는 반 정렬 상태이지 완전 정렬상태가 아니다! 힙 구조 + 정렬하는 과정

정렬 방법


초기상태 ==> 최대힙 ==> 정렬
  1. 최대 힙 만들기 (heapify)
  2. - 초기 상태의 배열을 별도의 배열 선언 없이 최대 힙으로 만들어야함 : 추가적인 메모리 공간을 생성하지 않고 원본 배열 안에서 정렬하기 위함. - 가장 작은 서브트리부터 최대 힙을 만족하도록 순차적으로 진행 - 이 상태는 정렬되어있지 않음! 힙은 최대값 또는 최소값을 빠르게 찾기 위한 자료구조
  3. 정렬하기
  4. -항상 루트 노드는 최대값을 갖고 있음. 최대값을 하나씩 삭제하면서 배열의 맨 뒤부터 채워나감 (root원소를 배열의 뒤로 보냄 -> 뒤에 채운 원소를 제외한 나머지 배열 원소들을 다시 최대힙을 만족하도록 재구성 )

코드


재귀 (Top-Down 형식)

public static void sort(int[] a) {
	sort(a,a.length);
}
private static void sort(int[] a, int size) {
	//원소가 1개이거나 0개일 경우 정렬할 필요가 없으므로 함수를 종료
	if (size < 2) return;
    
    //가장 마지막 요소의 부모 인덱스
    int parentIdx = getParent(size - 1);
    
    //최대힙 구성
    for(int i = parentIdx; i >= 0; i--) {
    	heapify(a, i, size - 1);
    }
    
    //root인 0번째인덱스와 i번째 인덱스의 값을 교환해준 뒤
    //0 ~ (i - 1)까지의 부분트리에 대해 max heap을 만족하도록 재구성
    for (int i = size - 1; i > 0; i--) {
    	swap(a, 0, i);
        heapify(a, 0, i - 1);
        //heapify하면 index : 0값에는 (0~i-1)값의 최대값이 있음
    }
}

//부모 인덱스를 얻는 함수
private static int getParent(int child) {
	return (child - 1) / 2;
}

//두 인덱스의 원소를 교환하는 함수
private static void swap(int[] a, int i, int j) {
	int temp = a[i];
    a[i] = a[j];
    a[j] = temp;
}

//힙을 재구성하는 함수
private static void heapify(int[] a, int parentIdx, int lastIdx) {
	
    //현재 트리에서 부모 노드의 자식노드 인덱스를 각각 구해준다.
    //현재 부모 인덱스를 가장 큰 값을 갖고있다고 가정한다.
    
    int leftChildIdx = 2 * parentIdx + 1;
    int rightChildIdx = 2 * parentIdx + 2;
    int largestIdx = parentIdx;
    
    //자식노드 인덱스가 트리의 크기를 넘어가지 않으면서, 현재 가장 큰 인덱스보다 왼쪽 자식노드의 값이 더 클경우, 가장 큰 인덱스를 가리키는 largestIdx를 왼쪽 자식노드 인덱스로 바꿔준다.
   	if(leftChildIdx <= lastIdx && a[largestIdx] < a[leftChildIdx]) {
    	largestIdx = leftChildIdx;
    }
    //자식 노드 인덱스가 트리의 크기를 넘어가지 않으면서, 현재 가장 큰 인덱스보다 오른쪽쪽 자식노드의 값이 더 클경우, 가장 큰 인덱스를 가리키는 largestIdx를 오른쪽 자식 노드인덱스로 바꿔준다.
    if(rightChildIdx <= lastIdx && a[largestIdx] < a[rightChildIdx]) {
    largestIdx = rightChildIdx;
	}
    
    //largestIdx와 부모노드가 같지 않다는 것은
    //위 자식 노드 비교 과정에서 현재 부모노드보다 큰 노드가 존재한다는 뜻이다.
    //그럴 경우 해당 자식 노드와 부모노드를 교환해주고,
    //교환된 자식 노드를 부모노드로 삼은 서브트리를 검사하도록 재귀 호출한다.
    if(parentIdx != largestIdx) {
    	swap(a, largestIdx, parentIdx);
        heapify(a, largestIdx, lastIdx);
    }
}   

장점 & 단점


-장점

  • 최악의 경우에도 O(NlogN)으로 유지
  • 부분 정렬을 할 때 효과가 좋다

-단점

  • 일반적인 O(NlogN) 정렬 알고리즘에 비해 성능이 떨어진다
  • 한번 최대힙을 만들면서 불안정 정렬 상태에서 최대값만 갖고 정렬을 하기 때문에 안정정렬이 아니다.

시간복잡도


heapify : 트리의 깊이만큼 비교 교환 = O(logN)

배열을 max heap으로 만드는 과정은 O(N)의 시간복잡도

힙 원소를 맨 뒤로 보낸 뒤 나머지 원소들 힙 재구성 : N개의 원소 * log(N) = O(NlogN)

profile
열정열정

0개의 댓글