알고리즘/정렬

[Java]정렬 : 힙 정렬(Heap Sort)

limwngur 2023. 8. 31. 10:50
728x90

Heap Sort는 Merge Sort와 Quick Sort만큼 빠른 정렬 알고리즘입니다. 

 

특징

  • 힙 트리 구조(Heap Tree Structure)을 이용하는 정렬 방법
  • 항상 O(N*logN)만족. tree구조를 사용하는 heap특성상 logN.
  • 병합 정렬과 달리 별도의 추가적인 배열 필요X -> 메모리 측면에서 효율적
  • 이론 적으로는 퀵,병합 정렬보다 우위. 하지만, 단순히 속도만 비교 시 퀵 정렬이 평균적으로 더 빠름
    때문에 힙 정렬이 일반적으로 많이 사용되지는 않음.
힙 정렬(Heap Sort)의 메커니즘은
힙(Heap)을 이용해 데이터를 정렬해보자!

필요한 선행 지식

  • 이진 트리(Binary Tree)란?
  • 완전 이진 트리(Complete Binary Tree)란?
  • 힙(Heap)이란?
이진 트리(Binary Tree)란?

컴퓨터 안에서 데이터를 표현하기 위해 데이터를 각 노드에 담은 뒤에 노드를 두 개씩 이어 붙이는 구조
이 때 트리 구조에 맞게 부모 노드에서 자식 노드로 가지가 뻗어갑니다.(서로 연결됨)

여기서 이진트리(Binary Tree) 모든 노드의 자식 노드가 2개 이하인 tree입니다.
Binary Tree Structure

※특정 노드의 인덱스 i에 대해서
RootNode : Arr[ (i-1) /2 ]
Left-Child : Arr[ 2i+1 ]
Right-Child : Arr[ 2i+2 ]
완전 이진 트리(Complete Binary Tree)란?

데이터가 루트노트부터 시작해서 자식 노드의 삽입 순서가 왼쪽 자식노드-오른쪽 자식노드로 차근차근 들어가는 구조의 이진 트리.

즉, 이진 트리 구조에서 중간에 노드가 비어있지 않고 왼쪽부터 차례대로 가득 찬 구조
Complete Binary Tree
힙(Heap)이란?

힙(heap) 구조는 최솟값,최댓값을 빠르게 찾아내기 위해 완전 이진 트리를 기반으로하는 트리구조.
힙에는 최대 힙(max heap)과 최소 힙(min heap)이 있다.

-최대 힙(Max Heap)
: '부모노드'가 '자식 노드'보다 크다.
최대 힙

-최소 힙(Min Heap)
:'자식 노드'가 '부모 노드'보다 크다.

-힙 생성 알고리즘(Heapify Algorithm)
: 특정한 노드의 두 자식 중에서 더 큰 자식과 자신의 위치를 바꾸는 알고리즘
: 해당 알고리즘을 통해 일반 Array로 선언된 tree구조를 heap으로 converting할 예정

힙 정렬(heap sort)를 활용해 7 6 5 8 3 5 9 1 6을 오름 차순 정렬해보자.

완전 이진 트리를 표현하는 가장 쉬운 방법

- 배열에 그대로 그대로 차례대로 삽입

 

0 1 2 3 4 5 6 7 8
7 6 5 8 3 5 9 1 6

이런 구조라고 생각하면 된다.

지금 위 이진 트리(binary tree)구조를 보면 heap구조가 아닌 것을 확인할 수 있다.

①heapify 알고리즘을 통해 힙(heap) 구조로 바꾸어 준다.

※트리 구조를 보면 알 수 있듯이 트리 구조를 이용하는 힙정렬의 시간복잡도가 왜 O(N*logN)인지 알 수 있다.
데이터의 총 갯수는 N개 이고, 힙 구조로 만드는 과정에서 heapify를 적용하려는 노드가 tree의 depth관점에서 logN의 수행 시간을 가지기 때문에 전체 트리를 힙 구조로 만드는 복잡도는 O(N*logN)입니다.

하지만!!!
사실상 모든 데이터를 heapify하지 않아도 되므로 O(N)에 가까운 속도를 낼 수 있다.
(이건 더 알아보고 차후 포스팅 해보자!)

After heapify

Heapifying이 된 이후 위 그림처럼 Heap구조로 바뀐다.

 

②이제, RootNode의 값과 마지막 인덱스에 해당하는 노드 값을 Swap한 후, 스와핑 때문에 깨진 힙 구조를 배열의 사이즈를 1개 줄여서(9와 6을 교체하고 9를 정렬된 요소로 fix!!) 다시 heap구조로 바꾸기 위해 heapifying을 한다.

 

이 과정이 계속되어 root에 있던 값이 점점 fix되면서 오름차순 정렬이 된다.

 


package sort;

public class Heap_sort {

    private static void heapify(int[] heap, int last) {

        // Bottom-up heapify(max heap)
        for (int i = 1; i < last; ++i) {
            int child = i;

            while (child > 0) {
                int root = (child-1)/2;
                if(heap[root]<heap[child]){
                    swap(heap,child,root);
                }
                child = root;
            };
        }
    }

    private static void heapSort(int[] heap) {
        int size = heap.length;

        // convertor: arr to heap
        heapify(heap,size);
        show(heap);
        System.out.println();
        // 크기 줄여가며 반복적으로 힙 구성
        for (int i = size - 1; i >= 0; --i) {
            swap(heap,0,i);
            heapify(heap,i);
        }
    }

    public static void main(String[] args) {
        int[] heap = {5,2,4,6,1,3};

        heapSort(heap); // 힙을 이용해서 정렬하는 메서드
        show(heap);

    }

    private static void swap(int[] data, int v1, int v2) {
        int temp = data[v1];
        data[v1] = data[v2];
        data[v2] = temp;
    }

    private static void show(int[] heap) {
        for (int val : heap) {
            System.out.print(val+" ");
        }
    }
}

※위 소스코드에서는 heapify를 특정한 노드를 기준으로 위쪽으로 점점 올라가는 상향식 구현 방식을 사용하였는데, 하향식 구현 방식도 존재한다.(두 방식 모두 시간 복잡도는 동일)

 

자료구조를 활용한 알고리즘이라 그런지 재밌다


코테에서는 java의 강점인 api를 사용하는 편이 좋은 것 같다.

우선 순위 큐(Priority Queue) 클래스를 통해서도 구현이 가능하다.

우선 순위 큐도 현재 리스트 내에 우선 순위가 높은 순서대로 원소를 반환하는 자료구조이기 때문이다.

 

package sort;

import java.util.Collections;
import java.util.PriorityQueue;
import java.util.Queue;

public class Heap_sort {

    //우선순위 큐 라이브러리를 이용한 힙 정렬
    public static void main(String[] args) {
        int[] data = {3, 7, 5, 4, 2, 8};
        int size = data.length;

        // 낮은 숫자가 우선 순위인 int형 우선순위 큐 선언
        Queue<Integer> heap = new PriorityQueue<>();

//      높은 숫자가 우선 순위인 int형 우선순위 큐 선언
//      Queue<Integer> heap = new PriorityQueue<>(Collections.reverseOrder());

        // arr to heap
        for (int i = 0; i < size; ++i) {
            heap.add(data[i]);
        }

        for (int val : heap) {
            System.out.print(val + " ");
        }
    }
}

아주 간단하게 코드가 작성되었다.

728x90