Algorithm

[알고리즘] 🤪 이죵과 병합정렬(Merge Sort)에 대해서 알아보자

이죵 2024. 8. 25. 20:51

오랜만에 작성하는 알고리즘 포스팅:)

오늘은 정렬중에 병합정렬 즉, 머지소트를 작성해보고자 한다.

때때로 알고리즘 시험중에 외부 라이브러리를 사용할 수 없어 직접 구현해야하는 경우가 있다.

그러니 이번기회에 개념을 정리하며 나와같이 머리에 넣어보도록 하자! 😚

🧁 병합 정렬(merge sort) 알고리즘?

  • 존 폰 노이만(John von Neumann)이라는 사람이 제안한 방법
  • 병합 정렬은 분할정복 방식과 재귀 알고리즘을 이용한 정렬알고리즘이다.
  • 즉, 주어진 배열을 원소가 하나밖에 남지 않을 떄까지 계속 둘로 쪼갠 후에 다시 크기순으로 재배열 하면서 원래 크기의 배열로 합치는걸 반복한다.
  • 병합정렬의 구조상 최대한 작게 문제를 쪼개어 앞의 부분 리스트부터 차례대로 합쳐나가기 때문에 안정정렬 알고리즘이기도 하다. 😀

🧁 병합 정렬(merge sort) 예시

[3, 2, 6, 7, 1, 8, 4, 5]

먼저 가운데를 기준으로 배열을 쪼갭니다.

[3, 2, 6, 7] [1, 8, 4, 5]

그리고 이걸 최소까지 쪼개봅시당.

[6, 5] [3, 1] [8, 7] [2, 4]

[6] [5] [3] [1] [8] [7] [2] [4]

더이상 쪼갤수가 없다면? 두개씩 다시 합쳐는데 합칠때는 작은 숫자가 앞에 큰 숫자가 뒤에 위치시키게 변경해 봅시다.

[5, 6] [1, 3] [7, 8] [2, 4]

그 이후에 2개를 또 4개로 합쳐봅시다! 똑같이 작은 숫자가 앞에 큰 숫자가 뒤에 위치시키게 변경합니다.

[1, 3, 5, 6] [2, 4, 7, 8]

최종적으로 남은 2개의 데이터들을 비교해가며 하나로 합치면 정렬된 배열을 얻을 수 있답니다!

[1, 2, 3, 4, 5, 6, 7, 8]

🧁 병합 정렬(merge sort) 특징

  • 병합정렬은 분할과 병합 단계로 나뉘고, 단순히 나뉘는 분할비용보다 모든 값들을 비교하는 병합 비용이 더 높습니다!
  • 8 → 4 → 2 → 1 로 반복되는 수가 점점 절반으로 줄어들 기 때문에 O(logN)의 시간이 필요하고, 병합시에는 모든 값을 비교해야하므로 O(N)의 시간이 소모됩니다.
    • 즉, 총 시간복잡도는 O(NlogN)입니다!
  • 두 개의 배열을 병합시 병합 결과를 담아놓을 배열이 추가로 필요하므로 공간복잡도는 O(N)입니다.

🧁 병합 정렬(merge sort) 구현

그럼 코드로 한번 살펴볼까용?

재귀를 이용해서 배열을 더 이상 나눌수 없을 때 까지 쪼갠 후 합쳐봅시당

Java

package Sort;

public class MergeSort {

    public static int[] sorted;
    public static StringBuilder sb;
    public static int N;      // 합치는 과정에서 정렬하여 원소를 담을 임시배열

    public static void merge_sort(int[] arr) {

        sorted = new int[arr.length];
        merge_sort(arr, 0, arr.length - 1);
        sorted = null;
    }

    // Top-Down 방식 구현
    private static void merge_sort(int[] arr, int left, int right) {

        // 더이상 쪼갤수 없는경우 return
        if (left == right) return;

        // 중간점
        int mid = (left + right) / 2;

        merge_sort(arr, left, mid);        // 왼쪽 부분배열(left ~ mid)
        merge_sort(arr, mid + 1, right);    // 오른쪽 부분배열(mid+1 ~ right)

        merge(arr, left, mid, right);        // 병합

    }

    /**
     * 병합구역 = arr배열의 left ~ right
     *
     * @param arr   정렬할 배열
     * @param left  배열의 시작점
     * @param mid   배열의 중간점
     * @param right 배열의 끝 점
     */
    private static void merge(int[] arr, int left, int mid, int right) {
        int l = left;        // 왼쪽 부분배열 시작점
        int r = mid + 1;    // 오른쪽 부분배열 시작점
        int idx = left;        // 채워넣을 배열의 인덱스

        while (l <= mid && r <= right) {

            // 왼쪽배열 l번째가 오른쪽배열 r번째보다 작거나 같은경우 왼쪽 l을 새배열에 넣고 l과 idx 1증가
            if (arr[l] <= arr[r]) {
                sorted[idx] = arr[l];
                idx++;
                l++;
            }
            // 오른쪽배열 r번째가 왼쪽배열 l번째보다 작거나 같은경우 오른쪽 r번째를 새배열에 넣고 r과 idx 1증가
            else {
                sorted[idx] = arr[r];
                idx++;
                r++;
            }
        }

        if (l > mid) { // 왼쪽배열 모두 새배열에 다 들어갔다면
            while (r <= right) { // 오른쪽 배열이 남아있다면
                sorted[idx] = arr[r]; // 오른쪽 부분배열에 나머지를 새 배열에 넣기
                idx++;
                r++;
            }
        } else { // 오른쪽 배열이 모두 새 배열에 들어갔다면
            while (l <= mid) { // 왼쪽 배열이 아직 남아있다면
                sorted[idx] = arr[l]; // 왼쪽 배열의 나머지를 새 배열에 넣기
                idx++;
                l++;
            }
        }

        sb.append("#").append(N++).append("\n");
        for (int i = left; i <= right; i++) { //정렬된 새 배열을 기존의 배열에 복사
            arr[i] = sorted[i];
            sb.append(arr[i]).append(" ");
        }
        sb.append("\n");
    }

    public static void main(String[] args) {
        int[] arr = new int[]{3, 2, 6, 7, 1, 8, 4, 5};
        sb = new StringBuilder();
        N = 0;
        merge_sort(arr);

        sb.append("#In arr").append("\n");
        for (int i = 0; i < arr.length; i++) { //정렬된 새 배열을 기존의 배열에 복사
            sb.append(arr[i]).append(" ");
        }
        sb.append("\n");

        System.out.println(sb);

    }

}

해당 개념을 잘 익혀서 알고리즘 시험때 써먹어봅시다! :)