Today's special moments become memories of tomorrow.

Algorithm & DS/정렬

힙 정렬(Heap Sort)

lotus lee 2021. 2. 22. 21:40

힙 정렬(Heap Sort)

힙(heap)을 이용하는 정렬 알고리즘

 

힙(heap) : 부모 노드의 값이 자식 노드의 값보다 큰 완전이진트리

부모 노드의 값이 자식 노드의 값보다 항상 크기 때문에 힙의 루트에는 제일 큰 수가 들어가게 된다.

이를 이용하여 힙의 루트 값을 꺼내어 배열의 오른쪽부터 채워넣으면서(오름차순 기준) 배열을 정렬할 수 있다.

 

힙으로 만들기 -> 배열의 맨 오른쪽부터 루트값 채워넣기 -> 다시 힙으로 만들기 -> 루트값 채워넣기  ...

이렇게 배열의 정렬이 완료될 때까지 힙 만들기와 루트값 넣기를 반복하여 정렬을 한다.

 

 

시간 복잡도 : O(NlogN)

힙으로 만들 때 걸리는 시간 복잡도는 O(NlogN), 힙에서 루트값을 꺼내는데 걸리는 시간 복잡도는 O(1) 이므로

힙 정렬의 시간 복잡도는 O(NlogN)이 된다.

 

 

 

[예제]

배열 arr의 요소가 5 9 3 1 8 6 4 7 2 일 때, 이 배열을 오름차순으로 정렬하기

 

배열 arr을 트리 형태로 나타내면 다음과 같다.

 

오른쪽 트리는 아직 힙 상태가 아니다. 부모 노드의 값이 자식 노드의 값보다 작은 경우가 존재하기 때문이다.

따라서 우선 오른쪽의 트리를 힙 상태로 바꿔줘야 한다.

 

힙이 아닌 트리를 힙 상태로 바꾸기

힙이 아닌 것을 힙으로 만들기 위해서 아래(Bottom)에서부터 위(Up)로 올라가며 힙 상태인지 아닌지 체크한다.

만약에 힙 상태가 아니라면 왼쪽 자식 노드 값과 오른쪽 자식 노드 값을 비교하여 더 큰 값과 부모 노드 값을 교환한다.

 

트리의 아래부터 위로 올라가면서 힙 상태가 아닌 것을 찾아보자.

아래 그림에서 빨간색 박스 안의 트리는 힙 상태가 아니다. 부모 노드의 값이 1로, 자식 노드 값인 2와 7보다 작기 때문이다.

 

그러면 자식 노드끼리 비교해보자.

왼쪽 노드의 값은 7, 오른쪽 노드의 값은 2 이므로 왼쪽 노드의 값이 더 크다.

그러므로 왼쪽 노드와 부모 노드를 서로 교환해준다.

1과 7을 교환함으로써 힙 상태가 되었다.

다음으로 아래의 빨간 박스 안의 트리를 보자. 

역시 마찬가지로 부모 노드의 값이 3으로, 자식 노드값인 6과 4보다 작으므로 힙 상태가 아니다.

방금전과 마찬가지로 자식노드끼리 비교해서 더 큰 값과 부모노드를 교환하면 힙 상태로 바뀐다.

이제 올라가서 위쪽을 보면 빨간 박스 안의 부분트리가 힙 상태가 아니다.

자식 노드 값인 9와 6을 비교하면 9가 더 크므로 왼쪽 자식 노드와 부모 노드를 교환한다.

위의 9와 5가 교환됨에 따라 아래의 빨간 박스 부분이 힙 상태가 아니게 된다.

동일한 방법으로 힙으로 만들어 주면 다음과 같다.

 

 

힙(heap) -> 정렬하기

이제 배열 전체를 힙 상태로 만들었다.

아래의 힙에서 루트에 있는 값인 9가 전체 중에서 가장 큰 값이 된다. 배열의 첫번째 요소에 가장 큰 값이 위치한다.

오름차순으로 정렬해야 하므로 루트에 있는 가장 큰 값이 배열의 맨 오른쪽에 있어야 한다.

그러므로 arr[0] = 9를 arr[8] = 2와 서로 교환한다.

배열의 맨 오른쪽 9는 정렬이 완료되었다.(회색칸은 정렬이 완료되었음을 의미)

이제 9를 제외한 arr[0] ~ arr[7] 까지를 다시 힙 상태로 만들면 9 다음으로 큰 값인 8이 루트 노드에 위치하게 된다.

루트 노드에 있는 8을 arr[7] 로 옮기기 위해서 arr[0]과 arr[7]을 교환한다.

다시 힙으로 만들면 루트 노드에 7이 들어간다.

arr[0]을 arr[6]의 값과 교환한다.

 

 

다시 힙으로 만들면 루트 노드에 6이 들어간다.

arr[0]을 arr[5]의 값과 교환한다.

 

 

다시 힙으로 만들면 루트 노드에 5가 들어간다.

arr[0]을 arr[4]의 값과 교환한다.

 

 

다시 힙으로 만들면 루트 노드에 4가 들어간다.

arr[0]을 arr[3]의 값과 교환한다.

 

 

다시 힙으로 만들면 루트 노드에 3이 들어간다.

arr[0]을 arr[2]의 값과 교환한다.

 

 

다시 힙으로 만들면 루트 노드에 2가 들어간다.

arr[0]을 arr[1]의 값과 교환한다.

 

 

arr[1] 까지 정렬을 완료하고 나면 위 과정을 종료한다.

이런식으로 반복적으로 힙을 만들어서 배열을 오름차순으로 정렬할 수 있다.

 

 

 

소스코드 : 

	public static void heapSort(int[] arr, int n) {

		for (int i = (n - 1) / 2; i >= 0; i--)
			makeHeap(arr, i, n - 1);

		for (int i = n - 1; i > 0; i--) {
			swap(arr, 0, i);
			makeHeap(arr, 0, i - 1);
		}
	}

	public static void makeHeap(int[] arr, int left, int right) {

		int root = arr[left]; // 루트 노드
		int parent; // 부모 노드 인덱스
		int child; // 자식 노드 인덱스

		for (parent = left; parent < (right + 1) / 2; parent = child) {

			int cl = parent * 2 + 1; // 왼쪽 자식 노드 인덱스
			int cr = cl + 1; // 오른쪽 자식 노드 인덱스

			child = (cr <= right && arr[cl] < arr[cr]) ? cr : cl;

			if (root > arr[child])
				break;

			arr[parent] = arr[child];
		}
		arr[parent] = root;

	}

'Algorithm & DS > 정렬' 카테고리의 다른 글

병합 정렬(Merge Sort)  (0) 2021.02.20
퀵 정렬(Quick Sort)  (2) 2021.02.17
단순 삽입 정렬(Straight Insertion Sort)  (0) 2021.02.16
단순 선택 정렬(Straight Selection Sort)  (0) 2021.02.16
버블 정렬(Bubble Sort)  (0) 2021.02.16