본문 바로가기
프로그래밍/JAVA

초보 자바 프로그래밍(21) - 병합정렬 (Merge Sort)

by 머니테크리더 2023. 5. 2.
반응형

병합 정렬 대표 이미지
병합 정렬 대표 이미지

 

 

병합 정렬(Merge Sort)은 분할 정복(Divide and Conquer) 방식을 사용하는 비교 기반 정렬 알고리즘입니다. 병합 정렬은 배열을 두 개의 동일한 크기의 하위 배열로 분할한 다음, 하위 배열을 정렬한 후 다시 병합하는 과정을 통해 정렬을 수행합니다. 병합 정렬은 안정적이며, 빠르게 작동하므로 큰 데이터 셋에 적합한 알고리즘입니다.

 

병합 정렬의 작동 원리

병합 정렬의 작동 원리는 다음과 같습니다:

  1. 배열의 크기가 1 또는 0이 될 때까지 배열을 절반으로 나누어 재귀적으로 분할합니다.
  2. 분할된 하위 배열을 정렬하고 병합하는 과정을 시작합니다. 이 때 하위 배열의 크기가 1이면 이미 정렬된 것으로 간주합니다.
  3. 두 개의 인접한 정렬된 하위 배열을 병합하여 새로운 정렬된 배열을 만듭니다. 병합 과정에서 두 하위 배열의 원소들을 비교하며 작은 값을 새 배열에 삽입하고, 해당 원소를 원래 하위 배열에서 삭제합니다. 이 과정을 두 하위 배열의 모든 원소가 새 배열에 삽입될 때까지 반복합니다.
  4. 모든 하위 배열이 병합되어 원래 배열의 크기와 동일한 크기의 정렬된 배열이 완성될 때까지 이 과정을 반복합니다.

 

병합 정렬의 특징

병합 정렬의 특징은 다음과 같습니다:

  1. 안정적인 정렬(Stable Sort): 같은 값의 원소들은 정렬 과정에서 상대적인 순서가 변경되지 않습니다.
  2. 비제자리 정렬(Out-of-place Sort): 정렬 과정에서 추가적인 메모리를 사용합니다. 이는 병합 과정에서 두 하위 배열을 새로운 배열에 합치는 과정에서 발생합니다.
  3. 시간 복잡도(Time Complexity): 최선, 평균, 최악의 경우 모두 O(n log n)의 시간 복잡도를 가집니다. 이로 인해 큰 데이터 셋에 적합한 알고리즘입니다.
  4. 재귀적(Recursive): 병합 정렬은 재귀적으로 배열을 분할하고 병합하는 과정을 수행합니다.

병합 정렬은 큰 데이터 셋에서 빠른 성능을 보이지만, 추가적인 메모리 사용량이 비교적 높습니다. 이 때문에 메모리 제약이 있는 상황에서는 다른 알고리즘을 사용하는 것이 좋습니다. 예를 들어, 퀵 정렬과 힙 정렬은 추가 메모리 사용량이 적은 대안이 될 수 있습니다.

 

 

병합 정렬의 장점

  • 안정적인 정렬: 같은 값의 원소들은 정렬 과정에서 상대적인 순서가 변경되지 않습니다.
  • 큰 데이터 셋에 효율적: 병합 정렬은 O(n log n)의 시간 복잡도를 가지므로 큰 데이터 셋에서도 빠른 성능을 보입니다.
  • 고르게 분할된 작업: 병합 정렬은 배열을 고르게 분할하여 작업을 수행하므로 최악의 경우에도 균일한 성능을 보입니다.

 

병합 정렬의 단점

  • 추가 메모리 사용: 병합 정렬은 두 하위 배열을 새로운 배열에 합치는 과정에서 추가적인 메모리를 사용합니다. 이로 인해 메모리 제약이 있는 상황에서는 더 적합한 알고리즘을 선택하는 것이 좋습니다.
  • 재귀적 구현: 병합 정렬은 재귀적으로 구현되어 있으므로, 스택 오버플로와 같은 문제가 발생할 수 있습니다. 이를 방지하기 위해 반복적인 구현 방법도 사용할 수 있습니다.

 

병합 정렬의 구현 예제

병합 정렬은 자바에서 다음과 같이 구현할 수 있습니다.

 

 

// MergeSort 클래스 선언
public class MergeSort {
    // 병합 과정을 수행하는 함수
    public static void merge(int[] arr, int left, int middle, int right) {
        // 구간의 크기를 계산
        int n1 = middle - left + 1;
        int n2 = right - middle;

        // 임시 배열 생성
        int[] leftArr = new int[n1];
        int[] rightArr = new int[n2];

        // 원래 배열에서 임시 배열로 데이터 복사
        System.arraycopy(arr, left, leftArr, 0, n1);
        System.arraycopy(arr, middle + 1, rightArr, 0, n2);

        // 인덱스 초기화
        int i = 0, j = 0, k = left;
        
        // 두 배열을 비교하면서 병합
        while (i < n1 && j < n2) {
            if (leftArr[i] <= rightArr[j]) {
                arr[k] = leftArr[i++];
            } else {
                arr[k] = rightArr[j++];
            }
            k++;
        }

        // 왼쪽 배열에 남은 원소를 복사
        while (i < n1) {
            arr[k++] = leftArr[i++];
        }

        // 오른쪽 배열에 남은 원소를 복사
        while (j < n2) {
            arr[k++] = rightArr[j++];
        }
    }

    // 병합 정렬 알고리즘을 수행하는 함수
    public static void mergeSort(int[] arr, int left, int right) {
        if (left < right) {
            // 중간 인덱스 계산
            int middle = (left + right) / 2;
            // 왼쪽 부분 배열 정렬
            mergeSort(arr, left, middle);
            // 오른쪽 부분 배열 정렬
            mergeSort(arr, middle + 1, right);
            // 병합
            merge(arr, left, middle, right);
        }
    }

    // 메인 함수
    public static void main(String[] args) {
        // 정렬할 배열
        int[] arr = {64, 34, 25, 12, 22, 11, 90};
        // 정렬 전 배열 출력
        System.out.println("Unsorted array:");
        for (int value : arr) {
            System.out.print(value + " ");
        }

        // 병합 정렬 실행
        mergeSort(arr, 0, arr.length - 1);

        // 정렬 후 배열 출력
        System.out.println("\nSorted array:");
        for (int value : arr) {
            System.out.print(value + " ");
        }
    }
}

병합 정렬은 재귀적 구현이 일반적이기 때문에 재귀 호출에 대한 깊이 제한이 적용되어 있을 경우, 깊이 제한에 도달하기 전에 정렬이 완료되지 않을 수 있습니다. 이 경우 반복적인 구현 방법을 사용하거나, 깊이 제한을 적절히 조절해야 합니다.

 

또한, 메모리 사용량이 큰 편이므로 메모리 제약이 있는 시스템에서 사용하기에는 부적합할 수 있습니다. 이러한 상황에서는 퀵 정렬, 힙 정렬 등 메모리 사용량이 상대적으로 적은 정렬 알고리즘을 고려해 볼 수 있습니다.

 

 

 

댓글