|
| 1 | +# Mergesort |
| 2 | + |
| 3 | +Goal: Sort an array from low to high (or high to low) |
| 4 | + |
| 5 | +Invented in 1945 by John von Neumann, mergesort is a fairly efficient sorting algorithm with a best, worst, and average time complexity of **O(n log n)**. The idea behind Mergesort |
| 6 | +is to **divide and conquer**; To divide a big problem into smaller problems and solving many small problems instead of solving a big one. I think of mergesort as **split first** and **merge after**. |
| 7 | + |
| 8 | +Assume you're given an array of *n* numbers and you need to put them in the right order. The merge sort algorithm works as follows: |
| 9 | + |
| 10 | +- Put the numbers in a pile. The pile is unsorted. |
| 11 | +- Split the pile into 2. Now you have **two unsorted piles** of numbers. |
| 12 | +- Keep splitting the resulting piles until you can't anymore; In the end, you will have *n* piles with 1 number in each pile |
| 13 | +- Begin to **merge** the piles together by sequentially pairing a pile with another pile. During each merge, you want to sort the contents in order |
| 14 | + |
| 15 | +## An example |
| 16 | + |
| 17 | +### Splitting |
| 18 | + |
| 19 | +Let's say the numbers to sort are `[2, 1, 5 , 4, 9]`. This is your unsorted pile. Our goal is to keep splitting the pile until you can't anymore. |
| 20 | + |
| 21 | +Split the array into two halves - `[2, 1,]` and `[5, 4, 9]`. Can you keep splitting them? Yes you can! |
| 22 | + |
| 23 | +Focus on the left pile. `[2, 1]` will split into `[2]` and `[1]`. Can you keep splitting them? No. Time to check the other pile. |
| 24 | + |
| 25 | +`[5, 4, 9]` splits to `[5]` and `[4, 9]`. Unsurprisingly, `[5]` can't split into anymore, but `[4, 9]` splits into `[4]` and `[9]`. |
| 26 | + |
| 27 | +The splitting process ends with the following piles: |
| 28 | + |
| 29 | +`[2]` `[1]` `[5]` `[4]` `[9]` |
| 30 | + |
| 31 | +### Merging |
| 32 | + |
| 33 | +Now that you've split the array, you'll **merge** the piles together **while sorting them**. Remember, the idea is to solve many small problems rather than a big one. For each merge iteration you'll only be concerned at merging one pile with another. |
| 34 | + |
| 35 | +Given `[2]` `[1]` `[5]` `[4]` `[9]`, the first pass will result in `[1, 2]` and `[4, 5]` and `[9]`. Since `[9]` is the odd one out, you can't merge it with anything during this pass. |
| 36 | + |
| 37 | +The next pass will merge `[1, 2]` and `[4, 5]` together. This results in `[1, 2, 4, 5]`, with the `[9]` left out again since it's the odd one out. |
| 38 | + |
| 39 | +Since you're left with 2 piles, `[9]` finally gets it's chance to merge, resulting in the sorted array `[1, 2, 4, 5, 9]`. |
| 40 | + |
| 41 | +### Top Down Implementation |
| 42 | + |
| 43 | +Based off of the above example, here's what mergesort may look like: |
| 44 | + |
| 45 | +```swift |
| 46 | +func mergeSort(array: [Int]) -> [Int] { |
| 47 | + guard array.count > 1 else { return array } // 1 |
| 48 | + let middleIndex = array.count / 2 // 2 |
| 49 | + |
| 50 | + let leftArray = mergeSort(Array(array[0..<middleIndex])) // 3 |
| 51 | + |
| 52 | + let rightArray = mergeSort(Array(array[middleIndex..<array.count])) // 4 |
| 53 | + |
| 54 | + return merge(leftPile: leftArray, rightPile: rightArray) // 5 |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | +A step-by-step explanation of how the code works: |
| 59 | + |
| 60 | +1. If the array is empty or only contains a single element, there's no way to split it into smaller pieces. You'll just return the array. |
| 61 | + |
| 62 | +2. Find the middle index. |
| 63 | + |
| 64 | +3. Using the middle index from the previous step, recursively split the left side of the resulting arrays. |
| 65 | + |
| 66 | +4. Using the middle index, recursively split the right side of the resulting arrays. |
| 67 | + |
| 68 | +5. Finally, merge all the values together, making sure that it's always sorted |
| 69 | + |
| 70 | + |
| 71 | +Here's the merging algorithm: |
| 72 | + |
| 73 | +```swift |
| 74 | +func merge(leftPile leftPile: [Int], rightPile: [Int]) -> [Int] { |
| 75 | + // 1 |
| 76 | + var leftIndex = 0 |
| 77 | + var rightIndex = 0 |
| 78 | + |
| 79 | + // 2 |
| 80 | + var orderedPile = [Int]() |
| 81 | + |
| 82 | + // 3 |
| 83 | + while leftIndex < leftPile.count && rightIndex < rightPile.count { |
| 84 | + if leftPile[leftIndex] < rightPile[rightIndex] { |
| 85 | + orderedPile.append(leftPile[leftIndex]) |
| 86 | + leftIndex += 1 |
| 87 | + } else if leftPile[leftIndex] > rightPile[rightIndex] { |
| 88 | + orderedPile.append(rightPile[rightIndex]) |
| 89 | + rightIndex += 1 |
| 90 | + } else { |
| 91 | + orderedPile.append(leftPile[leftIndex]) |
| 92 | + leftIndex += 1 |
| 93 | + orderedPile.append(rightPile[rightIndex]) |
| 94 | + rightIndex += 1 |
| 95 | + } |
| 96 | + } |
| 97 | + |
| 98 | + // 4 |
| 99 | + while leftIndex < leftPile.count { |
| 100 | + orderedPile.append(leftPile[leftIndex]) |
| 101 | + leftIndex += 1 |
| 102 | + } |
| 103 | + |
| 104 | + while rightIndex < rightPile.count { |
| 105 | + orderedPile.append(rightPile[rightIndex]) |
| 106 | + rightIndex += 1 |
| 107 | + } |
| 108 | + |
| 109 | + return orderedPile |
| 110 | +} |
| 111 | +``` |
| 112 | + |
| 113 | +This method is quite straightforward: |
| 114 | + |
| 115 | +1. You need 2 indexes to keep track of your progress for the two arrays while merging. |
| 116 | + |
| 117 | +2. This is the merged array. It's empty right now, but you'll build it up in subsequent steps by appending elements from the other arrays. |
| 118 | + |
| 119 | +3. This while loop will compare the elements from the left and right sides, and append them to the `orderedPile` while making sure that the result stays in order. |
| 120 | + |
| 121 | +4. If control exits from the previous while loop, it means that either `leftPile` or `rightPile` has it's contents completely merged into the `orderedPile`. At this point, you no longer need to do comparisons. Just append the rest of the contents of the other array until there's no more to append. |
| 122 | + |
| 123 | + |
| 124 | +Most implementations of Mergesort produce a *stable* sort. |
| 125 | + |
| 126 | +*"A sort is stable when elements that have identical sort keys remain in the same relative order after sorting. This is not important for simple values such as numbers or strings, but it is important when sorting more complex objects. In the example above, if two objects have the same `priority`, regardless of the values of their other properties, those two objects don't get swapped around." Matthijs Hollemans* |
| 127 | + |
| 128 | +### Performance |
| 129 | + |
| 130 | +The speed of mergesort is dependent on the size of the array it needs to sort. Whether or not the initial array is sorted already doesn't affect the speed of the sort since you'll be doing the same amount splits and comparisons irregardless of the initial order of the elements. |
| 131 | + |
| 132 | +Therefore, the time complexity for the best, worst, and average case will always be O(n log n). |
| 133 | + |
| 134 | +## See Also |
| 135 | + |
| 136 | +See also [Wikipedia](https://en.wikipedia.org/wiki/Merge_sort) |
| 137 | + |
| 138 | +*Written by Kelvin Lau* |
0 commit comments