Merge Sort

CSCI 1913 – Introduction to Algorithms, Data Structures, and Program Development
Adriana Picoral

Recursive Algorithm Design

  • What are base cases?
  • How can solving small problem help solve large problem?

Recursive Sorting

  • What is a “trivial” sorting problem (base case)?
  • What does it mean to solve small problem?
  • How do we combine small solutions?

Recursive Sorting

  • What is a “trivial” sorting problem (base case)?
    • list with 1 element
  • What does it mean to solve small problem?
    • sort a smaller list
  • How do we combine small solutions?
    • merge smaller sorted lists

Merge Operation (not in place)

Given:

  • Left, a sorted list size K
  • Right, a sorted list size G

Return:

  • Center, a sorted list size N = K + G
  • Center is a sorted version of Left + Right

Approach

Imagine you have two stacks of sorted cards, and you wanted to make one sorted pile in the center.

Main loop (until one stack is empty):

  • if “top” value of left stack is smaller put it on top of the center stack
  • otherwise put top of right stack on top of the center stack

Aftewards move leftover stack onto center.

Merge

def merge (left, right):
  center = []
  l = 0
  r = 0
  
  while l < len(left) and r < len(right):
    if left [l] < right [r]:
      center.append(left[l])
      l += 1
    else:
      center.append(right[r])
      r += 1
      
  while l < len(left):
    center.append(left[l])
    l += 1
  
  while r < len(right):
    center.append(right[r])
    r += 1
  
  return center

Runtime

  • Body of all loops are O(1) (append is O(1))
  • Each run through the loop appends exactly once
  • End list is size N = G + K, so all lists run O(N) times.
  • Merge is an O(N) = O(G + K) algorithm.

Merge sort

Merge sort

  • What is a “trivial” sorting problem (base case)?
    • list with 1 element
  • What does it mean to solve small problem?
    • sort a smaller list
  • How do we combine small solutions?
    • merge smaller sorted lists (solved with merge)

Merge sort

Class “divide and conquer” algorithm

def merge_sort(lst):
    if len(lst) == 1:
        return lst
    else:
        mid = len(lst)//2
        left = lst[:mid]
        right = lst[mid:]

        left = merge_sort(left)
        right = merge_sort(right)

        return merge(left, right)

Correctness of recursive algorithms

  • Demonstrate the recursion will halt
  • Demonstrate each base-case is correct
  • Assume recursive calls return correctly – demonstrate recursive case is right.

Correctness of recursive algorithms

  • Recursion halts: Argument – each recursive call is a shorter list (it reaches the base case)
  • Base case is right – 1 element list is sorted
  • So long as recursive call does its thing, merge can finish the job

Runtime

\(O(N) work + recursion case\)

At each level we have linear work across all subproblems combined, and there are only logarithmically many levels.

  • merge does O(N) work
  • There are log(N) “layers”

The array is divided in half \(\log N\) times (the depth of the recursion tree). At each level, we do \(O(N)\) work to merge all subarrays at that level.

Total work is \(O(N) * \log N\) = \(O(N \log N)\)

Runtime

The recurrence relation is \(T(N) = 2*T(\frac{N}{2}) + O(N)\)

  • \(2*T(\frac{N}{2})\) represents recursively sorting two halves of the array
  • \(O(N)\) is the time to merge the two sorted halves back together

Total runtime is \(O(N \log N)\)