본문 바로가기
알고리즘

[알고리즘] 병합 정렬 (Merge Sort)

by Hongwoo 2022. 7. 28.
반응형

목차

    병합 정렬이란?

    병합 정렬은 분할 정복 (Divide and Conquer) 방식을 이용해서 하나의 리스트를 두 개의 리스트로 분할한 다음 각각의 분할된 리스트를 정렬한 후에 합해서 정렬된 하나의 리스트로 만드는 정렬 알고리즘이다. 추가로 병합 정렬은 퀵 정렬과 달리 정렬을 할 때 데이터 크기만큼의 추가 공간이 필요하므로 제자리 정렬 (in-place sort)이 될 수는 없다.

     

    병합 정렬은 분할 정복의 다음과 같은 과정을 거친다.

     

    분할(Divide) : 리스트를 두 개의 리스트로 분할한다

    정복(Conquer) : 분할된 리스트를 정렬한다. 

    결합(Combine): 정렬된 두 개의 리스트를 하나의 정렬된 리스트로 결합한다.

     

    이제 병합 정렬의 예시를 한번 살펴보겠다.

     

     

    병합 정렬 병합 과정

    분할된 리스트를 하나의 정렬된 리스트로 합칠 때 정렬을 하면서 합치게 된다. 그리고 이때 걸리는 시간 복잡도는 O(N)이다. 이 이유는 이미 정렬된 리스트들을 합치기 때문이다. 조금 더 구체적으로 병합하는 과정을 살펴보도록 하겠다.

     

    1. 두 개의 리스트의 값을 처음부터 비교해서 두 개의 리스트의 값 중에 작은 값을 새로운 리스트로 옮긴다.

    2. 두 개의 리스트 중 하나의 리스트가 끝날 때까지 반복한다.

    3. 하나의 리스트가 끝나게 되면 남은 리스트의 값을 모두 새로운 리스트로 옮긴다.

     

     

     

    소스 코드

     

    import java.io.*;
    import java.util.*;
    public class Main {
        static int number = 15;  //데이터 개수
        static int[] sorted = new int[number];  //정렬된 배열
    
        static void merge(int[] a, int m, int mid, int n) {
            int i = m;
            int j = mid + 1;
            int k = m;
            // 두 배열 중 하나를 다 옮길 때 까지 반복
            while (i <= mid && j <= n) {
                if (a[i] < a[j]) {
                    sorted[k] = a[i];
                    i++;
                } else {
                    sorted[k] = a[j];
                    j++;
                }
                k++;
            }
    
            //남아있는 데이터 삽입
            if (i > mid) {
                for (int t = j; t <= n; t++) {
                    sorted[k] = a[t];
                    k++;
                }
            } else {
                for (int t = i; t <= mid; t++) {
                    sorted[k] = a[t];
                    k++;
                }
            }
    
            //정렬된 배열을 기존 배열에 삽입
            for (int t = m; t <= n; t++) {
                a[t] = sorted[t];
            }
        }
    
        static void mergeSort(int[] a, int m, int n) {
            if (m < n) {
                int mid = (m + n) / 2;  //배열의 중간간
                mergeSort(a, m, mid);
                mergeSort(a, mid + 1, n);
                merge(a, m, mid, n);
            }
        }
        
        public static void main(String[] args) throws IOException {
            int[] arr = {1, 5, 10, 12, 4, 3, 8, 9, 11, 2, 13, 15, 6, 14, 7};  //정렬할 배열
            mergeSort(arr, 0, arr.length-1);
            for (int i = 0; i < number; i++) {
                System.out.print(arr[i] + " ");
            }
        }
    }

     

    이제 이 코드를 이용해서 다음과 같은 배열을 정렬해보겠다

     

    배열 = {1, 5, 10, 12, 4, 3, 8, 9, 11, 2, 13, 15, 6, 14, 7};

     

     

    즉, 다음과 같이 1부터 15까지 정렬되어 있는 것을 확인할 수 있다.

     

     

    병합 정렬 시간 복잡도

     

     

    병합 정렬의 시간 복잡도는 n개의 데이터를 가지고 logN단계를 거치기 때문에 O(NlogN)이 된다. 퀵 정렬은 피봇을 어떻게 선택하느냐에 따라 최악의 시간 복잡도가 O(N^2)가 되지만 병합 정렬은 항상 리스트를 절반으로 나누기 때문에 O(NlogN)의 시간 복잡도를 보장한다.

     

     

    병합 정렬 장단점

    장점

    • 퀵 정렬은 데이터의 분포에 따라 최악의 경우 O(n2)의 시간 복잡도를 갖는데 병합 정렬은 O(nlogn)의 시간 복잡도를 보장한다
    • 선택 정렬, 삽입 정렬같이 O(n2)의 시간 복잡도를 가지고 있는 정렬 알고리즘보다 더 효율적이다

     

    단점

    • 데이터가 배열로 구성된 경우 임시 배열이 필요하므로 추가 공간이 필요하다. → 추가 공간을 사용하므로 제자리 정렬이 아니다
    • 데이터의 크기가 큰 경우 이동 횟수가 많으므로 시간 낭비 발생

     

     

     

    반응형

    댓글