Software Carpentry logo

Computational Complexity

1) Overview

2) What is Complexity?

3) Measuring Runtime: the "Stopwatch" Approach

4) Measuring Runtime: an Abstract Measure

5) Counting Steps

def LinearSearch(A, x):
    i = 0              # line 1
    while i < len(A):  # line 2
        if A[i] = x:   # line 3
            return i   # line 4
        i = i + 1      # line 5
    return -1          # line 6

6) Expressing Runtime

7) Generalizing the Measure

^                                                                      
| (number of steps)     W                                              
|                       .                                              
|                   W   .        Each point represents number of steps 
|                   :   :        executed by algorithm on one input.   
|               W   .   :        'W's correspond to worst-case runtime;
|               .   :   .        'B's correspond to best-case runtime; 
|           W   :   .   .        average-case runtime falls somewhere  
|           .   .   :   :        in between (depending on probability  
|       W   :   :   .   .        distribution over inputs).            
|       :   .   .   B   B                                              
|   W   .   B   B                                                      
|   B   B                                                              
 --------------------------> (input size)                              

Figure 12.1: Graph of Runtime Measures

8) Which Measure to Use?

9) Asymptotic Notation

10) Big-Oh

11) Big Omega and Big Theta

12) Subtleties

13) Upper Bounds and Lower Bounds

def LinearSearch(A, x):
    i = 0              # line 1
    while i < len(A):  # line 2
        if A[i] = x:   # line 3
            return i   # line 4
        i = i + 1      # line 5
    return -1          # line 6

14) Upper Bounds

15) Lower Bounds

16) Binary Search

# Precondition:  A is sorted in non-decreasing order, i.e.,
# A[0] <= A[1] <= ... <= A[n-1] (where n = len(A)).
def BinarySearch(A, x):
    b = 0
    e = len(A)
    # Invariant:  if x appears in A, it appears in A[b:e] (A[e] excluded).
    while b <= e:
        m = (b + e) / 2
        if x < A[m]:  e = m
        else:  b = m
    return b  # if x appears in A, it appears at A[b]

17) Runtime of Binary Search

18) Common Sorting Algorithms

def selection_sort(list):
    ''' Sort list in non-decreasing order, using Selection Sort. '''
    # Invariant: list[0:i] is sorted, list[0:i] <= list[i:].
    for i in range(len(list)):
        # Find minimum element in list[i:].
        m = i
        # Invariant: list[m] <= list[i:j].
        for j in range(i+1,len(list)):
            if list[j] < list[m]:  m = j
        # Put minimum element at index i.
        list[i], list[m] = list[m], list[i]
def insertion_sort(list):
    ''' Sort list in non-decreasing order, using Insertion Sort. '''
    # Invariant: list[0:j] is sorted.
    for j in range(1,len(list)):
        # Find correct position for list[j] within list[0:j].
        i = j
        # Invariant: list[0:i] is sorted, list[i:j+1] is sorted,
        #   list[0:i] <= list[i+1:j+1].
        while i >= 1 and list[i] < list[i-1]:
            list[i-1], list[i] = list[i], list[i-1]
            i -= 1
def merge_sort(list):
    ''' Sort list in non-decreasing order, using Merge Sort. '''
    # If list contains at most 1 element, it is already sorted.
    if len(list) <= 1:  return
    # Recursively sort each half of list.
    middle = len(list) / 2
    merge_sort(list[:middle])
    merge_sort(list[middle:])
    # Merge the sorted halves into a temporary list.
    temp = []
    left = 0
    right = middle
    # Invariant: 0 <= left <= middle <= right <= len(list), temp contains
    #   the merged contents of list[:left] with list[middle:right], every
    #   element in temp is less than or equal to every element in
    #   list[left:middle] and list[right:].
    while len(temp) < len(list):
        if right == len(list) or (left < middle and list[left] <= list[right]):
            temp.append(list[left])
            left += 1
        else:
            temp.append(list[right])
            right += 1
    # Copy the temporary list back into list.
    list[:] = temp
import random

def quick_sort(list):
    ''' Sort list in non-decreasing order, using Quick Sort. '''
    # If list contains at most 1 element, it is already sorted.
    if len(list) <= 1:  return
    # Select a pivot element at random, then partition the list.
    pivot = random.choice(list)
    smaller = [x for x in list if x < pivot]
    equal   = [x for x in list if x == pivot]
    greater = [x for x in list if x > pivot]
    # Recurse and copy the results back into list.
    quick_sort(smaller)
    quick_sort(greater)
    list[:] = smaller + equal + greater

19) Runtimes of Sorting Algorithms

Algorithm Worst-case Average-case Best-case Notes
Selection Sort Θ(n2) Θ(n2) Θ(n2) depends only on size, not on particular input
Insertion Sort Θ(n2) Θ(n2) Θ(n) best for input already sorted (or close)
Merge Sort Θ(n log n) Θ(n log n) Θ(n log n) requires additional space Θ(n)—compared to Θ(1) for all others
Quick Sort Θ(n2) Θ(n log n) Θ(n log n) worst for input already sorted (or close)

Table 12.1: Runtimes of Common Sorting Algorithms

20) Last Words on Sorting

21) Abstract Dictionaries

Data Structure lookup insert delete
unsorted linked list Θ(n) Θ(1) Θ(1)
unsorted array Θ(n) Θ(1) Θ(1)
sorted array Θ(log n) Θ(n) Θ(n)
binary search tree Θ(n) Θ(n) Θ(n)
balanced search tree Θ(log n) Θ(log n) Θ(log n)
hash tables Θ(n) Θ(n) Θ(n)

Table 12.2: Worst-case Complexity of Dictionary Operations

22) Binary Search Trees (BSTs)

      ______50_________         
     /                 \        
   20                ___75___   
  /  \              /        \  
10    30          60          90
        \        /  \        /  
         40    55    65    80   

Figure 12.2: Binary Search Tree Example

23) Balanced Search Trees

    y        right rotation         x    
   / \       -------------->       / \   
  x   C                           A   y  
 / \          left rotation          / \ 
A   B        <-------------         B   C

Figure 12.3: Illustration of Tree Rotations

24) Direct-Access Tables

25) Hash Tables

26) Collisions and Chaining

27) Lookup in Hash Table with Chaining