Data Structures and Algorithms

spring 2024

Merge sort

Merge sort is an efficient sorting algorithm with time complexity \(O(n \log n)\).

The algorithm uses recursion to first sort the first half and the second half of the list separately. Then it merges the two sorted halfs into the full sorted list. Once the two halfs are sorted, the merging can be implemented efficiently.

Recursion is used so that the algorithm calls itself to sort the two list halfs. The recursion ends when the list has only one element remaining, since a one element list is already in order.

Example

The following picture illustrates merge sort on the list \([5,1,2,9,7,5,4,2]\).

Implementation

Merge sort can be implemented in Python as follows:

def merge_sort(items):
    n = len(items)

    if n <= 1: return

    left = items[0:n//2]
    right = items[n//2:]

    merge_sort(left)
    merge_sort(right)

    a = b = 0
    for i in range(n):
        if b == len(right) or \
           (a < len(left) and left[a] < right[b]):
            items[i] = left[a]
            a += 1
        else:
            items[i] = right[b]
            b += 1
        
numbers = [5, 2, 4, 2, 6, 1]
merge_sort(numbers)
print(numbers) # [1, 2, 2, 4, 5, 6]

The function merge_sort sorts the given list using merge sort. If the list has at most one element, the function does nothing. Otherwise, the function splits the list into two halfs left and right and sorts them recursively.

After sorting the two halfs, the algorithms constructs the full sorted list. To do this, the algorithm goes through the positions of the list from left to right and fills each position with the smallest remaining element from the lists left and right. Since the two half lists are already in order, the smallest remaining element in each is found efficiently.

Notice that the above function merge_sort is intended for illustrating how merge sort works and not as an example of the best way to implement sorting in Python. It is better to use the built-in implementations sort and sorted in Python.

Efficiency

The time complexity \(O(n \log n)\) of merge sort comes from the facts that there are \(O(\log n)\) levels of recursion and that the merging on each level takes \(O(n)\) time. The number of recursion levels is \(O(\log n)\), because when a list of length \(n\) is halved \(\log n\) times, the list has only one element remaining and the recursion ends.

The merging on each level happens as follows:

The total number of elements processed at each level is \(O(n)\), and thus the merging time per level is \(O(n)\).