Skip to main content
Logo image

Section 22.10 Runtime Analysis and Big-O Notation

So far weโ€™ve mostly focused on whether our code and algorithms are correct. But when working with large datasets, it also matters how much work an algorithm has to do. Runtime analysis is our way of estimating how the amount of work (time, memory, โ€ฆ) grows as the input size \(n\) becomes large.
Rather than counting the exact number of operations (which would depend on the specific computer, compiler, and even the specific input), we focus on the dominant term and ignore constant factors and lower-order terms. This is captured by Big-O notation: we say an algorithm runs in \(O(f(n))\) time if, for sufficiently large \(n\text{,}\) the runtime never exceeds \(c \cdot f(n)\) for some constant \(c\text{.}\)
Consider linear search:
#include <stdio.h>
int main(void) {
    int list[] = {6, -2, 5, 12, 7, 3, 8, 15, -10, 1};
    int n = 10;
    int item, i, found;
    printf("What number are you interested in?");
    scanf("%d", &item);
    i = 0;
    found = 0;
    while(!found && i<n){
        if(list[i] == item){
            found = 1;
        }
        else{
            i++;
        }
    }
    return (0);
}
In the worst case (the item is last, or not present at all) the while loop runs \(n\) times, so linear search is \(O(n)\)โ€” linear time.
Now consider bubble sort:
#include<stdio.h>
int main(void) {
    int n = 10;
    int i, j, swap;
    int list[] = {759, 14, 2, 900, 106, 77, -10, 8, -3, 5};

    for(j=0; j<n-1; j++){
        for(i=0; i<n-1; i++){
            if(list[i] > list[i+1]){
                swap = list[i];
                list[i] = list[i+1];
                list[i+1] = swap;
            }
        }
    }
    return (0);
}
Here we have two nested loops, each running roughly \(n\) times, so the inner comparison happens roughly \(n^2\) times: bubble sort is \(O(n^2)\)โ€” quadratic time.
Runtime analysis generally involves:
  • Counting primitive operations (comparisons, swaps, assignments) as a function of \(n\)
  • Loop analysis: multiplying the number of iterations by the cost of whatโ€™s inside the loop
  • Recurrence relations for recursive algorithms (like QuickSort!)
We also distinguish three common cases:
  • Best-caseโ€” the smallest possible cost
  • Average-caseโ€” the expected cost, assuming a reasonable distribution of inputs
  • Worst-caseโ€” the largest possible cost
Here are the typical growth classes, from fastest to slowest:
Figure 22.1. Typical growth classes, plotted as runtime (or step count) versus input size \(n\)
Table 22.2. Common Runtime Classes
Notation Name Example
\(O(1)\) constant accessing an array element
\(O(\log n)\) logarithmic binary/bisection search
\(O(n)\) linear linear search
\(O(n \log n)\) linearithmic merge sort, heapsort, QuickSort (average case)
\(O(n^2)\) quadratic bubble sort, selection sort, insertion sort, QuickSort (worst case)
\(O(n^3)\) cubic naive matrix multiplication
\(O(2^n)\) exponential brute-force subset-sum
\(O(n!)\) factorial traveling-salesperson brute force
Where does QuickSort fit in? On average, each partitioning step is \(O(n)\text{,}\) and the list roughly halves in size each time, giving \(O(n \log n)\) overallโ€” much better than bubble sortโ€™s \(O(n^2)\text{!}\) But in the worst case (for example, an already-sorted list with a poorly-chosen pivot) QuickSort degrades to \(O(n^2)\text{,}\) which is exactly why the median-of-three pivot strategy from the previous section is worth using in practice.

Check Your Understanding Check Your Understanding

1.

An algorithm has two nested loops, each of which runs \(n\) times regardless of the input. What is its Big-O runtime?
  • \(O(n^2)\)
  • Correctโ€” two nested loops each running \(n\) times gives roughly \(n \times n = n^2\) operations.
  • \(O(n)\)
  • Not quiteโ€” that would be the case for a single loop, not two nested ones. Try again!
  • \(O(\log n)\)
  • Not quite. Try again!
  • \(O(1)\)
  • Not quite. Try again!