Efficiency Analysis
CSC108H1 introduced big-O informally. Here it becomes a tool for choosing between the structures you have built: which operations each does cheaply, which expensively, and how to analyse a recursive function whose cost is not obvious from looking at it.
- Define big-O in terms of growth, and say what it ignores.
- Analyse loops, nested loops and recursive calls.
- Compare the structures from Units 3, 4, 7 and 8 by operation.
- Distinguish worst, average and amortised cost.
Every structure in this course is a trade-off. Analysis is how you make the trade deliberately rather than by habit, and it is the only way to compare two correct programs.
A quadratic algorithm on a thousand items is fine and on a million is hopeless. Most performance problems in production are not slow code — they are the wrong growth rate discovered after the data grew.
The notation is Bachmann's, from 1894 number theory. Knuth brought it into computing in 1976. Whether every problem verifiable in polynomial time is also solvable in polynomial time — P versus NP — remains open and carries a million-dollar prize.
What big-O means
A function is $O(g(n))$ if, beyond some input size, its cost is at most a constant multiple of $g(n)$. Informally: how the work grows as the input grows.
An $O(n)$ algorithm with a huge constant can lose to an $O(n^2)$ one on small inputs. Big-O tells you which wins eventually, which is the right question when data grows and the wrong one when it never will.
Analysing code
for x in xs: ... # O(n)
for y in xs: ... # O(n) - total still O(n)
for x in xs: # O(n)
for y in xs: ... # x O(n) = O(n^2)Two passes are $2n$, which is $O(n)$. Nested passes multiply, and that is the difference that matters.
out = []
for x in xs:
if x not in out: # this is a loop! O(len(out))
out.append(x)One visible loop, $O(n^2)$ total. The operations that hide a loop are
in on a list, list.remove,
list.insert(0, x), list.pop(0), slicing, and string
concatenation. Each turns an enclosing loop quadratic.
Count the calls and multiply by the work per call.
def total(obj): # nested list, n integers total
if isinstance(obj, int):
return obj
return sum(total(item) for item in obj)def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)def total_rec(xs: list) -> int:
if xs == []:
return 0
return xs[0] + total_rec(xs[1:])xs[1:] copies the rest of the
list each time. Copying $n$, then $n-1$, and so on gives $O(n^2)$ —
hidden entirely inside the slice.Comparing the structures
Every one of these is a one-line change that alters the growth rate, which is why the table is worth memorising.
Worst, average, amortised
A Python list keeps spare capacity. Most appends drop into a free slot, which is $O(1)$. When it fills, the list allocates a bigger block and copies everything — $O(n)$.
Say which case you mean. "A dictionary is $O(1)$" is shorthand for the average, and the distinction matters when an adversary chooses the input.
• Writing $O(2n)$ or $O(n + 3)$. Drop constants: both are
$O(n)$.
• Missing hidden loops inside list operations and
slicing.
• Quoting BST or dictionary complexity without saying which case you
mean.
• Assuming fewer lines means faster. A one-line comprehension can be
quadratic.
• Optimising growth rate on data that will never be large. Sometimes
$O(n^2)$ is the right answer.
Practice
Write out each answer before opening it. Original exercises written for this guide in the style of the course's weekly work.
What is the complexity of two sequential loops over the same list?
Answer
What is the complexity of building a list with
if x not in out inside a loop?
Answer
How do you make that $O(n)$?
Answer
Why is recursing with xs[1:] quadratic?
Answer
You need fast membership and fast sorted output. What do you use?
Answer
Explain why list.append is called amortised
$O(1)$.
Answer
What is the complexity of visiting every node of a tree with $n$ values?
Answer
When is an $O(n^2)$ algorithm acceptable?
Answer
Expanded topic checklist
Course coverage. This unit is part of the complete CSC148H1 topic map. It should cover the following items before you move on:
- Big-O. Growth rates, dropping constants and lower-order terms.
- Reading code. Sequential versus nested, and the operations that hide a loop.
- Recursive analysis. Counting calls times work per call.
- Case distinctions. Worst, average and amortised, and when each is the honest figure to quote.
The calendar names "analyzing the efficiency of programs". Check Quercus for the assigned exercises; complexity questions appear on every term test in this course.
Cost grows with input. Structures differ in which operations are cheap. Choosing one is choosing which operations you are willing to pay for, and analysis is how that choice gets made on evidence.
Deep practice extensions
Use these after the main practice set. They push on definitions and edge cases rather than routine calculation.
Why is $3n^2 + 5n$ written $O(n^2)$?
Guide
How do sequential and nested loops combine?
Guide
Name three operations that are secretly $O(n)$.
Guide
x in a_list,
list.pop(0), and slicing. Each makes an enclosing loop
quadratic.How do they differ?