알고리즘 - 힙정렬&이진트리&힙) 복습을 위해 작성하는 글 2023-04-09

rizz·2023년 4월 9일
0

알고리즘

목록 보기
6/15

📒 갈무리 - 힙정렬(Heap Sort)&이진트리(Binary Tree)&힙(Heap)

📌 힙정렬이란?

- 완전 이진 트리 구조를 가진 자료구조이다.

- 힙의 특성을 이용하여 정렬하는 방법이다.

- 최솟값 혹은 최댓값 빠르게 가져오기 위해 고안된 방법이다.

 

📌 이진 트리(Binary Tree)

- 컴퓨터 내에서 데이터를 표현할 때 데이터를 각 노드(Node)에 담은 뒤에 노드를 두 개씩 이어 붙이는 구조

- 부모에서 자식으로 뻗어 나가는 구조

- 한 부모가 가질 수 있는 자식 노드의 수는 최대 2개이다.

- 데이터는 부모의 왼쪽부터 들어가게 된다.

트리(Tree) : 가지가 뻗어나가는 것처럼 데이터가 서로 연결되어 있는 구조

루트(Root) : 트리의 최상단 노드

리프(Leaf) : 가장 아래에 있는 노드들

 

📌 완전 이진 트리(Complete Binary Tree)

- 이진 트리의 노드가 중간에 비어있지 않고 모두 채워져있는 구조

 

📌 힙(Heap)

- 힙은 완전히 정렬된 자료구조가 아니라 부모 자식 간에만 부분 적으로 정렬된 자료구조이다.

- 힙은 부모 노드가 자식 노드보다 항상 크거나 작다.

- 최솟값이나 최댓값을 빠르게 찾아내기 위해 완전 이진 트리를 기반으로 하는 트리 구조

- 최대 힙과 최소 힙 이 존재한다.

최대 힙 : 부모 노드가 자식 노드보다 큰 힙

최소 힙 : 부모 노드가 자식 노드보다 작은 힙

- 오름차순 정렬일 땐, 최대 힙을 구성

- 내림차순 정렬일 땐, 최소 힙을 구성

- 힙정렬을 하기 위해서는 데이터를 힙 구조(최대 힙 또는 최소 힙)를 갖도록 만들어야 한다.

- 힙정렬을 수행하기 위해서는 '힙 생성 알고리즘(Heapify Algoritm)'을 사용해야 한다.

 

📌 힙 생성 알고리즘(Heapify Algoritm)

- 어떤 노드에 대해서 힙 구조가 붕괴되었을 때 그 노드의 자식들 중에서 더 큰 값 또는 더 작은 값을 부모로 변경하는 알고리즘

- 모든 정점에 대해 힙 생성 알고리즘을 수행한다면 힙 구조가 된다.

- O(logN)의 시간 복잡도를 갖는다.

- 힙 생성 알고리즘을 전체 원소의 개수만큼 반복해서 수행하면 정렬이 이루어지기 때문에 힙 정렬의 시간 복잡도는 O(N*logN)이 된다.

- 상향식과 하향식 구현 방식이 있다.

- 상향식 : 아래쪽에서 위쪽으로 올라가며 힙 구조를 만드는 방식

- 하향식 : 위에서 아래쪽으로 내려가며 힙 구조를 만드는 방식

- 상향식이냐 하향식이냐에 따라 힙 구조가 다를 순 있지만, 힙 구조를 만드는 것이 목적이기 때문에 어떤 방식을 사용하느냐는 상관이 없다.

 

💡 TIP

실제로는 데이터의 개수를 반으로 나눈 횟수만큼만 수행을 하더라도 힙 구조를 만들 수 있기 때문에 실제 시간 복잡도는 O(n/2*logN)이 될 수 있다.

 

📌 힙정렬 진행 순서

1. n개의 노드에 대해 완전 이진 트리를 구성한다.

2. 최대 힙 또는 최소 힙을 구성한다.

3. 가장 큰 수(루트 노드)를 가장 작은 수와 교환한다.(최대 힙 기준)

4. 2와 3을 반복한다.

 

📌 직접 구현해 보자...

// C#
        public void HeapSort(int[] data)
        {
            int root;
            int c;
            for (int j = data.Length - 1; j >= 0; j--)
            {
                for (int i = 1; i <= j; i++) // 전체 트리 구조를 힙구조로 변경
                {
                    c = i;
                    while (c != 0) // 최상단 부모와 비교할 때까지
                    {
                        root = (c - 1) / 2;
                        if (data[root] < data[c])
                        {
                            Swap(ref data[root], ref data[c]);
                        }
                        else
                        {
                            break;
                        }
                        c = root;
                    }
                }
                Swap(ref data[0], ref data[j]); // 가장 큰 값과 마지막 값을 변경
            }

        }

        public void Swap(ref int number1, ref int number2)
        {
            int temp = number1;
            number1 = number2;
            number2 = temp;
        }
        // 가독성을 위해 각 기능마다 함수로 분리하면 좋지만, 지금은 편의상 간단하게 구현함

 

📌 힙정렬의 특징

- 병합정렬, 퀵정렬과 마찬가지로 O(N*logN)의 시간 복잡도를 갖는다.

- 병합정렬과는 다르게 별도의 추가적인 배열이 필요하지 않기 때문에 메모리 측면에서 보다 효율적이다.

- 항상 O(N*logN)의 시간 복잡도를 보장한다는 점에서 강력한 정렬 알고리즘이라 할 수 있다.

profile
복습하기 위해 쓰는 글

0개의 댓글