-> 블로그 이전

[Algorithm Week_3] MergeSort

2022. 3. 16. 21:30Major`/알고리즘

MergeSort

Stable Sort


Recursion으로 생각?

1. 배열을 2등분

2. 2등분 한 각 subarray들을 정렬 "By Recursion"

3. 정렬된 2개의 subarray들을 "Merge"

 

최종 "Merge" 방식

i는 왼쪽 subarray 전용 포인터이고, j는 오른쪽 subarray 전용 포인터이다

- 여기서 i, j가 가리키는 각 값들을 비교해서 최종 정렬된 배열에 넣어준다

  • 오름차순 배열이 default이므로, 더 작은 값을 넣어준다

 

1) i : 4 & j : 1

4 > 1 이므로, j가 가리키는 값인 1을 배열에 넣어주고 j는 증가시켜준다

 

 

2) i : 4 & j : 2

4 > 2 이므로, j가 가리키는 값인 2를 배열에 넣어주고 j는 증가시켜준다

 

3) i : 4 & j : 5

4 < 5 이므로, i가 가리키는 값인 4를 배열에 넣어주고 i는 증가시켜준다

 

 

4) i : 6 & j : 5

6 > 5 이므로, j가 가리키는 값인 5를 배열에 넣어주고 j는 증가시켜준다

 

 

5) i : 6 & j : 10

6 < 10 이므로, i가 가리키는 값인 6을 배열에 넣어주고 i는 증가시켜준다

 

6) i : 8 & j : 10

8 < 10 이므로, i가 가리키는 값인 8을 배열에 넣어주고 i는 증가시켜준다

 

7) i : 9 & j : 10

9 < 10 이므로, i가 가리키는 값이 9를 배열에 넣어주고 i는 증가시켜준다

 

 

8) i : out & j : 10

i는 왼쪽 subarray 전용 포인터이다. 그런데 i는 지금 해당 영역을 벗어났다. 이 의미는 왼쪽 subarray의 모든 요소들은 이미 최종 배열에 정렬이 완료되었다는 의미이고, 이제 포인터 j의 영역의 요소들을 전부 최종 배열에 넣어주면 된다

 


Java Code

static void MergeSort(int [] list, int left, int right){
    /*
    list는 우리가 코드를 작성할 때는 그냥 recursion으로 던져버리지만,
    상세히 보면 list는 결국 계속해서 쪼개지면서 최종적으로는 길이가 1인 list가 된다
    */

    if(left < right){
        int mid = (left + right) / 2;
        MergeSort(list, left, mid);
        MergeSort(list, mid + 1, right);
        Merge(list, left, mid, right);
    }
}

static void Merge(int [] list, int left, int mid, int right){
    int i = left;
    int j = mid + 1;
    /*
    1. Recursion적으로 생각해보면 우리는 결국 하나의 Array를 2개의 subarray로 나누었고
    2. 2개의 subarray를 각각 정렬 후 (By Recursion)
    3. 정렬된 2개의 subarray를 이 메소드에서 최종적으로 "Merge"한다
    >> 따라서, "int i"는 왼쪽 subarray의 영역을 담당하고, "int j"는 오른쪽 subarray의 영역을 담당하도록 설정
    >> "int i & int j"는 본인이 담당하는 영역을 벗어나면 안된다
     */

    for(int k=left; k<=right; k++){
        if(j > right){
            /*
            "int j"는 오른쪽 subarray를 담당하고, 오른쪽 subarray의 마지막 index는 "int right"이다
            조건식에서 "j > right"는 "int j"는 본인이 담당하는 영역을 벗어낫다는 의미이다
            >> 따라서 오른쪽 subarray는 모두 '배열 MergeSortedArray'로 옮겨진 상태이고
                이 상태에서는 "int i"가 담당하는 영역의 요소들만 '배열 MergeSortedArray'로 옮겨주면 된다
             */
            MergeSortedArray[k] = list[i++];
        }
        else if(i > mid){
            /*
            "int i"는 왼쪽쪽 subarray를 담당하고, 왼쪽 subarray의 마지막 index는 "mid"이다
            조건식에서 "i > mid"는 "int i"는 본인이 담당하는 영역을 벗어낫다는 의미이다
            >> 따라서 왼쪽 subarray는 모두 '배열 MergeSortedArray'로 옮겨진 상태이고
                이 상태에서는 "int j"가 담당하는 영역의 요소들만 '배열 MergeSortedArray'로 옮겨주면 된다
             */
            MergeSortedArray[k] = list[j++];
        }
        else if(list[i] < list[j]){
            /*
            이 조건식에 도달했다는 의미는 "int i & int j"가 현재 본인들의 영역에서 벗어나지 않았다는 의미이다
            그러면 "MergeSort"를 기본적으로 ASC 배열을 만들려고 하기 때문에 작은 값을 MergeSortedArray로 옮기면 된다
             */
            MergeSortedArray[k] = list[i++];
        }
        else{
            MergeSortedArray[k] = list[j++];
        }
    }
}

 

시간복잡도 분석

- Recursion의 시간복잡도는 일단 점화식을 구해서 점화식을 이용해서 최종적인 시간복잡도를 구해야 한다

static void Merge(int [] list, int left, int mid, int right){
    int i = left;
    int j = mid + 1;

    for(int k=left; k<=right; k++){
        if(j > right){
            MergeSortedArray[k] = list[i++];
        }
        else if(i > mid){
            MergeSortedArray[k] = list[j++];
        }
        else if(list[i] < list[j]){
            MergeSortedArray[k] = list[i++];
        }
        else{
            MergeSortedArray[k] = list[j++];
        }
    }
}

일단 Merge함수의 시간복잡도는 O(n)이다

 

static void MergeSort(int [] list, int left, int right){
    if(left < right){
        int mid = (left + right) / 2;
        MergeSort(list, left, mid);
        MergeSort(list, mid + 1, right);
        Merge(list, left, mid, right);
    }
}

MergeSort(int [] list, int left, int right)의 연산 수행 횟수를 T(n)이라고 하면 (n은 list의 길이)

이 점화식을 풀면 최종적으로 MergeSort의 시간복잡도는 O(n log n)으로 표현된다

 

 

 

MergeSort는 언제나 시간복잡도가 O(n log n)으로 표현이 된다 (최악 / 평균 / 최선)

 


Full Code (Java)

package Week_3;

public class Merge_Sort {

    static int [] MergeSortedArray;

    public static void main(String[] args) {
        int [] list = {4, 6, 8, 9, 1, 2, 5, 10};
        MergeSortedArray = new int[list.length];

        System.out.println("정렬 전 list []");
        for(int n : list){
            System.out.print(n + " ");
        }

        MergeSort(list, 0, list.length - 1);

        System.out.println("\n\n정렬 후 list []");
        for(int n : MergeSortedArray){
            System.out.print(n + " ");
        }
    }

    static void MergeSort(int [] list, int left, int right){
        /*
        list는 우리가 코드를 작성할 때는 그냥 recursion으로 던져버리지만,
        상세히 보면 list는 결국 계속해서 쪼개지면서 최종적으로는 길이가 1인 list가 된다
        */

        if(left < right){
            int mid = (left + right) / 2;
            MergeSort(list, left, mid);
            MergeSort(list, mid + 1, right);
            Merge(list, left, mid, right);
        }
    }

    static void Merge(int [] list, int left, int mid, int right){
        int i = left;
        int j = mid + 1;
        /*
        1. Recursion적으로 생각해보면 우리는 결국 하나의 Array를 2개의 subarray로 나누었고
        2. 2개의 subarray를 각각 정렬 후 (By Recursion)
        3. 정렬된 2개의 subarray를 이 메소드에서 최종적으로 "Merge"한다
        >> 따라서, "int i"는 왼쪽 subarray의 영역을 담당하고, "int j"는 오른쪽 subarray의 영역을 담당하도록 설정
        >> "int i & int j"는 본인이 담당하는 영역을 벗어나면 안된다
         */

        for(int k=left; k<=right; k++){
            if(j > right){
                /*
                "int j"는 오른쪽 subarray를 담당하고, 오른쪽 subarray의 마지막 index는 "int right"이다
                조건식에서 "j > right"는 "int j"는 본인이 담당하는 영역을 벗어낫다는 의미이다
                >> 따라서 오른쪽 subarray는 모두 '배열 MergeSortedArray'로 옮겨진 상태이고
                    이 상태에서는 "int i"가 담당하는 영역의 요소들만 '배열 MergeSortedArray'로 옮겨주면 된다
                 */
                MergeSortedArray[k] = list[i++];
            }
            else if(i > mid){
                /*
                "int i"는 왼쪽쪽 subarray를 담당하고, 왼쪽 subarray의 마지막 index는 "mid"이다
                조건식에서 "i > mid"는 "int i"는 본인이 담당하는 영역을 벗어낫다는 의미이다
                >> 따라서 왼쪽 subarray는 모두 '배열 MergeSortedArray'로 옮겨진 상태이고
                    이 상태에서는 "int j"가 담당하는 영역의 요소들만 '배열 MergeSortedArray'로 옮겨주면 된다
                 */
                MergeSortedArray[k] = list[j++];
            }
            else if(list[i] < list[j]){
                /*
                이 조건식에 도달했다는 의미는 "int i & int j"가 현재 본인들의 영역에서 벗어나지 않았다는 의미이다
                그러면 "MergeSort"를 기본적으로 ASC 배열을 만들려고 하기 때문에 작은 값을 MergeSortedArray로 옮기면 된다
                 */
                MergeSortedArray[k] = list[i++];
            }
            else{
                MergeSortedArray[k] = list[j++];
            }
        }
    }
}