ABOUT ME

Today
Yesterday
Total
  • MergeSort (with Swift)
    DataStructure + Algorithm/Basic Theories 2021. 10. 20. 01:24

     Merge Sort

    큰 덩어리를 작게 나누고, 나뉘어진 것들을 sorting 한 후 합치면서 정렬하는 방법을 Merge Sort 라고 합니다. 

     

    예를 들어 10개의 elements 가 있는 경우, 해당 array 를 sorting 하려면 위에서 썼던 방법들을 사용했을 때 O(n^2), 약 100 번의 과정이 소요된다고 가정해볼게요.

     

    만약 array 를 반으로 나눈 후 sorting, 합칠때 다시 sorting 해주면 과정이 많이 줄어들 수 있습니다. 위와 같은 방식으로 한다면 5^2 이 두번, 그리고 합치는 경우에는 모든 element 들을 한번씩 비교하고 배치해야하니 10번 으로 잡으면 (25 + 25 + 10 ) 60번 이네요. 

     

    그런데말입니다, 이러한 과정을 끝없이 (각 element 가 하나가 될 때까지) 하면 어떻게 될까요 ?? 

    우선 과정을 big O 표기법으로 정리해보면 다음과 같습니다.

     

    1. n 개의 elements 를 가진 1개의 array -> 1 개의 element 를 가진 n 개의 array      O(n)

    2 개 -> 1회

    4 개 -> 3회

    8 개 -> 7회

    16 개 -> 15회

    ...

     

     

    2. 비교, 정렬 (16개 element 로 가정)

    1 x 16개 2개씩 짝지어 비교 -> 16 회

    2 x 8 개 2개씩 짝지어 비교 -> 16 회

    4 x 4 개 2개씩 짝지어 비교 -> 16 회

    8 x 2 개 2개씩 짝지어 비교 -> 16 회

    16 x 1 개 2개씩 짝지어 비교 -> 16 회

     

    정리

    1. 과정은 n 개의 elements 일 때 나누는 데 총 n 번이 필요합니다. ( O(n))

    2. 과정은 n 회의 비교과정이 log n 번 만큼 일어나므로 총 n * log n 번이 필요합니다. (O(n log n))

    따라서, mergeSort 은 O(n log n) 의 Time Complexity 를 갖습니다.

     

     

     

     

     

     

    예시를 통해 과정을 설명해드릴게요 ! 

    0.  8 개의 element 가 array 에 있다고 가정해볼게요.

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

    1. 이 elements 반으로 나눕니다. 나눈 후 정렬하면 더 빠르게 할 수 있으니까요.

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

    2. 더더 빠르게 하기 위해서 또다시 나눕니다.

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

    3. 더더더 빠르게 하기 위해서 다시 한번 나눕니다.

     

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

    4. 이제 나눌수가 없습니다. 지금부터 합치면서 정렬을 시작하겠습니다.

     

    5. 각 상자에 있는 element 를 앞에서부터 2개씩, 보고 비교해서 작은 값을 앞에, 큰 값을 뒤에 둡니다. (2개 들어있는 상자 4 생성 )

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

    6. 2개의 상자들 (2개의 elements 가 들어있는) 에서 각각의 element 들을 비교 후, 4 * 1 상자에 넣습니다. (4개 들어있는 상자 2 생성)

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

    7. 위 과정을 반복합니다. (8 * 1 의 상자가 생겼고, 정렬이 완료되었습니다.) 

     

     

     

     

     

    5, 6, 7 과정을 사실 똑같아요. 과정이 많이 축약되어 있는 것 같아 과정에 대해 추가로 설명드리겠습니다.

    // [5, 6], [1, 3], [7, 8], [2, 4]
    
     [5,6] ~ [1, 3] 비교
    1.   51 비교 ->  1 이 더 작으므로 1을 우선 배치  [1]
    2.   53 비교 ->  3 이 더 작으므로 3을 그 후에 배치 [1, 3]
    3.   5, 6 비교할 대상 없으므로 그 뒤에 배치 [1, 3, 5, 6]
    
    [7,8], [2,4] 비교
    1.   72 비교 ->  2 이 더 작으므로 2을 우선 배치  [2]
    2.   74 비교 ->  4 이 더 작으므로 4을 그 후에 배치 [2, 4]
    3.   7, 8 비교할 대상 없으므로 그 뒤에 배치 [2, 4, 7, 8]
    
    [1, 3, 5, 6], [2, 4, 7, 8]

     

     

    다음으로, Swift 로 구현한 MergeSort 입니다. 

    func mergeSort1(_ array:[Int]) -> [Int]{
    
        if array.count > 1 {   // if array has more than 1 element, divide it.
            var firstHalfArray = [Int]()  // initiate empty Int array
            var lastHalfArray = [Int]()
            
            let half = array.count / 2
    
            for i in 0 ..< array.count {
                if i < half { // first half of array is going to be 'firstHalfArray'
                    firstHalfArray.append(array[i])
                } else {      // last half of array
                    lastHalfArray.append(array[i])
                }
            }
            return merge(mergeSort1(firstHalfArray), mergeSort1(lastHalfArray))
        } // if it has a only element, return self 
        return array
    }

     

     

     

    func merge(_ array1: [Int], _ array2: [Int]) -> [Int] { // merge only sorted!! array
    
        var resultArray = [Int]()
        var array1Index = 0
        var array2Index = 0
    
        while (array1.count > array1Index || array2.count > array2Index) {
        
            // validity check for both array1 and array2
            
            // if one of arrayIndexes is equal to the number of array it's using for,
            //  append the other array to the end of resultArray. then, return!
            
            if array1Index == array1.count {
                // need to merge remained arrays , not the whole part.
                for index2 in array2Index ..< array2.count {
                    resultArray.append(array2[index2])
                }
                break
            }
            
            if array2Index == array2.count {
                for index1 in array1Index ..< array1.count {
                    resultArray.append(array1[index1])
                }
                break
            }
            
            // compare elements one by one. If one(a) is smaller than or equal to the other(b), 
            //put first one(a) at the end of resultArray, and add 1 to the index of that array
    
    	if array1[array1Index] <= array2[array2Index] {
                resultArray.append(array1[array1Index])
                array1Index += 1
            } else {
                resultArray.append(array2[array2Index])
                array2Index += 1
            }
        }
    
        return resultArray
    }

    mergeSort 를 위와 같이 두개의 function 을 이용하여 구현하였습니다. 

     

    첫번째 함수 mergeSort1(_) 은 하나의 array 를 반으로 나누고, 나누어진 배열을 'return merge(mergeSort1(_), mergeSort2(_))' 

    안에 넣어줍니다. (Recursion) 

    단, element 가 하나일 때는 element를 반환합니다. (terminating condition for recursion)

     

    merge 함수는 이미 정렬된 두 array 들만 정렬할 수 있는 함수이고, 실제로 merge 함수에는 이미 정렬된 array 만 parameter 로 들어오게 됩니다. 

     

    예를들어  [4,3,2,1] 을 mergeSort1 을 이용한다고 했을 때 흐름은 아래와 같습니다.

    mergeSort1(_:) input:[4, 3, 2, 1]
    mergeSort1(_:) input:[4, 3]  // first part of merge(mergeSort[4,3], [2,1])begin
    mergeSort1(_:) input:[4]
    mergeSort1(_:) input:[3]
    merge(_:_:)    input:[4], [3] // first part ended
    
    mergeSort1(_:) input:[2, 1]   // second part of merge(mergeSort[4,3], [2,1]) begin
    mergeSort1(_:) input:[2]
    mergeSort1(_:) input:[1]
    merge(_:_:)    input:[2], [1] // second part ended
    
    merge(_:_:)    input:[3, 4], [1, 2]

     

    merge 함수에서 전체적인 과정은 다음과 같습니다. 

    1. 각 sorted 된 array 에서 가장 앞에 있는 element 두개 (a,b) 를 비교 후, 더 작거나 같으면 그 element (a)를 result Array 에 넣고, 사용된 array 내 다음 element (a' ~ b)와 다시 비교합니다. 해당 과정을 하나의 행렬의 마지막 element 가 사용될 때까지 반복합니다.

     

    2. 하나의 행렬이 모두 사용되었으면, result Array 에 나머지 행렬 중 사용되지 않은 부분을 마지막에 붙입니다. 

    (코드에서 해당 부분 순서는 바뀌었습니다.)

     

     

    수고 많으셨습니다. Quick Sort 와 MergeSort 는 Sorting 에서 많이 쓰이는 Algorithm 이므로 꼭 알아가시면 좋을 것 같습니다. 다음으로는 Quick Sort 에 대해 포스팅하도록 하겠습니다. 

     

    Generic 을 사용했을 때 각 function type 에 대해 적는 것을 끝으로 글을 마치겠습니다. 긴 글 읽어주셔서 감사합니다 

    func mergeSort1 <T: Comparable>(_ array:[T]) -> [T]
    func merge <T: Comparable> (_ array1: [T], _ array2: [T]) -> [T]

    정말 마지막으로, merge 에서 각 element 를 비교해야 하기 때문에 T 는 Comparable protocol 을 Conform 해야 합니다.

     

    그리고, Swift - Core Library - Darwin 에 이미 mergesort 가 있기때문에 mergeSort1 로 이름 지은 점 참고해주세요.

    (물론 parameter, return type, 함수 이름 (s, S) 이 모두 다르기때문에 다른 function 이지만요.. )

    'DataStructure + Algorithm > Basic Theories' 카테고리의 다른 글

    Greedy Algorithm  (0) 2021.10.22
    Quick Sort with Swift  (0) 2021.10.21
    Dynamic Programming (with Fibonacci sequence(using Swift and Cache))  (0) 2021.10.19
    LinkedList  (0) 2021.10.13
    Stack, Queue With Swift  (0) 2021.10.12
Designed by Tistory.