CMPUT-204 · Lesson 2 of 12 · 30 min
Asymptotic notation
Lesson 1 argued that growth rate matters more than constant factors. This lesson makes that idea precise. Asymptotic notation lets us say that one function grows no faster than another, no slower, or at the same rate, while ignoring constant factors and small inputs. The notation is compact, but every statement in it is a claim with a definition behind it, and you can prove or disprove such claims with the techniques from CMPUT-272. By the end of the lesson you will be able to classify running times, prove bounds with explicit constants, and avoid the common ways the notation is misused.
Big-O: an upper bound
Let \(f\) and \(g\) be functions from the natural numbers to the non-negative real numbers. We say \(f(n)\) is \(O(g(n))\), read big-O of \(g\), if there are positive constants \(c\) and \(n_0\) such that \(f(n) \le c \cdot g(n)\) for every \(n \ge n_0\). In words: beyond some point, \(f\) never exceeds a constant multiple of \(g\). The constant \(c\) absorbs differences in machine speed and implementation detail; \(n_0\) lets us ignore small inputs where lower-order terms might dominate.
The definition is a nested-quantifier statement of exactly the kind you translated in CMPUT-272 lesson 4. To prove \(f \in O(g)\) you must exhibit specific values of \(c\) and \(n_0\) and then show the inequality for all larger \(n\). To disprove it you must show that for every choice of \(c\) and \(n_0\) there is some \(n \ge n_0\) where \(f(n) > c \cdot g(n)\). Writers often use the equals sign, as in \(3n^2 + 5n = O(n^2)\), but it means membership in a set of functions, not equality: it is never correct to flip it around and write \(O(n^2) = 3n^2 + 5n\).
Worked problem
Proving a big-O bound with constants
Prove that 3n² + 5n + 2 is O(n²).
Show solution
- We need c and n₀ with 3n² + 5n + 2 ≤ c·n² for all n ≥ n₀.
- For n ≥ 1, 5n ≤ 5n² and 2 ≤ 2n², because n ≥ 1 implies n ≤ n² and 1 ≤ n².
- So 3n² + 5n + 2 ≤ 3n² + 5n² + 2n² = 10n² for all n ≥ 1.
- Choose c = 10 and n₀ = 1; the definition is satisfied.
Answer: With c = 10 and n₀ = 1, 3n² + 5n + 2 ≤ 10n² for every n ≥ 1, so the function is O(n²). Other constants work too, for example c = 4 with n₀ = 6.
The trick in that proof, replacing each lower-order term by a larger multiple of the highest-order term, works for any polynomial. It shows that a polynomial of degree \(d\) with a positive leading coefficient is \(O(n^d)\). It also shows that constants are not unique: if \(c = 10\) works, so does \(c = 11\), and a larger \(n_0\) often allows a smaller \(c\).
def check(f, g, c, n0, upto=10**5):
"""Check f(n) <= c*g(n) for n0 <= n <= upto; return first failure or None."""
for n in range(n0, upto + 1):
if f(n) > c * g(n):
return n
return None
f = lambda n: 3 * n * n + 5 * n + 2
g = lambda n: n * n
print('c=10, n0=1 :', check(f, g, 10, 1))
print('c=4, n0=6 :', check(f, g, 4, 6))
print('c=4, n0=1 :', check(f, g, 4, 1)) # fails for small n
print('c=3, n0=1 :', check(f, g, 3, 1)) # c too small: fails at every nc=10, n0=1 : None c=4, n0=6 : None c=4, n0=1 : 1 c=3, n0=1 : 1
The check is useful for finding constants before you prove them, in the explore-then-prove style of CMPUT-272 lesson 1. Look at what the last line shows: with \(c = 3\), the bound fails immediately, because \(3n^2 + 5n + 2 > 3n^2\) for every \(n\). No choice of \(n_0\) rescues \(c = 3\); you must increase \(c\).
Big-Omega and Big-Theta
Big-O is an upper bound. The matching lower bound is big-Omega: \(f(n)\) is \(\Omega(g(n))\) if there are positive constants \(c\) and \(n_0\) with \(f(n) \ge c \cdot g(n)\) for every \(n \ge n_0\). When both hold, \(f\) and \(g\) grow at the same rate and we write \(f(n) \in \Theta(g(n))\), read big-Theta. Equivalently, there are constants \(c_1, c_2 > 0\) and \(n_0\) with \(c_1 g(n) \le f(n) \le c_2 g(n)\) for all \(n \ge n_0\).
Worked problem
A Theta bound
Prove that n(n − 1)/2, the number of pairs among n items, is Θ(n²).
Show solution
- Upper bound: n(n − 1)/2 ≤ n²/2 for all n ≥ 1, so it is O(n²) with c = 1/2 and n₀ = 1.
- Lower bound: for n ≥ 2, n − 1 ≥ n/2, so n(n − 1)/2 ≥ n · (n/2)/2 = n²/4.
- So it is Ω(n²) with c = 1/4 and n₀ = 2.
- Both bounds hold, so n(n − 1)/2 is Θ(n²), with c₁ = 1/4, c₂ = 1/2 and n₀ = 2.
Answer: n(n − 1)/2 ∈ Θ(n²). The pairwise duplicate check from lesson 1 therefore makes Θ(n²) comparisons in the worst case.
Big-O, Omega and Theta can describe any function, not only running times. Statements such as the worst-case running time of insertion sort is \(\Theta(n^2)\) and its best-case running time is \(\Theta(n)\) are both precise and informative. Saying merely that insertion sort is \(O(n^2)\) is true but weaker, because \(O\) only gives an upper bound: insertion sort is also \(O(n^3)\) and \(O(2^n)\), which are true and useless. When you know a tight bound, state it with Theta.
| Notation | Informal meaning | Analogy with numbers |
|---|---|---|
| f ∈ O(g) | f grows no faster than g | a ≤ b |
| f ∈ Ω(g) | f grows no slower than g | a ≥ b |
| f ∈ Θ(g) | f grows at the same rate as g | a = b |
| f ∈ o(g) | f grows strictly slower than g | a < b |
| f ∈ ω(g) | f grows strictly faster than g | a > b |
Little-o, little-omega and the limit test
Sometimes we want to say that one function grows strictly slower than another. We write \(f \in o(g)\), little-o, if for every constant \(c > 0\) there is an \(n_0\) with \(f(n) < c\, g(n)\) for all \(n \ge n_0\). The quantifier on \(c\) has changed from there exists to for every, which is what makes the relation strict. Similarly \(f \in \omega(g)\) means \(g \in o(f)\). For example \(n \in o(n^2)\), but \(n^2 \notin o(n^2)\).
Limits often make classification quick. If the limit of \(f(n)/g(n)\) as \(n\) goes to infinity exists and equals \(L\), then: \(L = 0\) means \(f \in o(g)\) and hence \(f \in O(g)\); \(0 < L < \infty\) means \(f \in \Theta(g)\); and \(L = \infty\) means \(f \in \omega(g)\) and hence \(f \in \Omega(g)\). The test does not apply when the limit fails to exist, for example for functions that oscillate, so the definitions remain the final authority.
import math
pairs = [('n log2 n', 'n^2', lambda n: n * math.log2(n), lambda n: n * n),
('n^2', '2^n', lambda n: n * n, lambda n: 2 ** n),
('(log2 n)^3', 'n', lambda n: math.log2(n) ** 3, lambda n: n),
('5n^2 + n', 'n^2', lambda n: 5 * n * n + n, lambda n: n * n)]
for fn, gn, f, g in pairs:
ratios = [f(n) / g(n) for n in (2**4, 2**8, 2**12, 2**16)]
print(f'{fn:>11} / {gn:<4}', ' '.join(f'{r:.4g}' for r in ratios)) n log2 n / n^2 0.25 0.03125 0.00293 0.0002441
n^2 / 2^n 0.003906 5.66e-73 0 0
(log2 n)^3 / n 4 2 0.4219 0.0625
5n^2 + n / n^2 5.062 5.004 5 5The ratios suggest the limits: \(n \log n / n^2\) heads to 0, \(n^2 / 2^n\) collapses to 0 very fast, and \(5n^2 + n\) over \(n^2\) approaches 5, so it is Theta of \(n^2\). The third row is instructive: \((\log_2 n)^3 / n\) starts at 4 when \(n = 16\), meaning the cubed logarithm is still larger than \(n\), and only then falls toward 0. Any power of a logarithm grows more slowly than any positive power of \(n\), but the crossover can be far out: \((\log_2 n)^{10}\) stays above \(n\) until about \(n = 2^{59}\), which is more than \(5 \times 10^{17}\). Numerical ratios are evidence; L'Hôpital's rule or a direct argument provides the proof.
The hierarchy of common functions
The following chain, in which each function is little-o of the next, covers almost every running time in this course: \(1\), \(\log n\), \(\sqrt{n}\), \(n\), \(n \log n\), \(n^2\), \(n^3\), \(2^n\), \(n!\), \(n^n\). Three facts underlie it. Every logarithm grows more slowly than every positive power: \((\log n)^k \in o(n^\epsilon)\) for all \(k\) and every \(\epsilon > 0\). Every polynomial grows more slowly than every exponential with base greater than 1: \(n^k \in o(b^n)\) for \(b > 1\). And \(n!\) lies between \(2^n\) and \(n^n\), with Stirling's approximation showing \(\log_2(n!) \in \Theta(n \log n)\), a fact lesson 5 uses for the sorting lower bound.
The base of a logarithm does not matter inside asymptotic notation, because \(\log_a n = \log_b n / \log_b a\) and \(1/\log_b a\) is a constant. That is why we write \(O(\log n)\) without a base. The base of an exponential does matter: \(3^n\) is not \(O(2^n)\), since \(3^n / 2^n = 1.5^n\) grows without bound.
Rules for combining bounds
Analysing an algorithm usually means combining bounds for its parts. A few rules, each provable from the definitions, cover most cases. Constant factors can be dropped: if \(f \in O(g)\) then \(k f \in O(g)\) for any constant \(k > 0\). Sums are dominated by the larger term: \(f_1 + f_2 \in O(\max(g_1, g_2))\) when \(f_1 \in O(g_1)\) and \(f_2 \in O(g_2)\). Products multiply: \(f_1 f_2 \in O(g_1 g_2)\). And the relation is transitive: \(f \in O(g)\) and \(g \in O(h)\) give \(f \in O(h)\). The same rules hold for Omega and Theta.
| Code pattern | Rule used | Bound |
|---|---|---|
| Two loops one after another, each Θ(n) | Sum rule | Θ(n) |
| A Θ(n) loop containing a Θ(n) inner loop | Product rule | Θ(n²) |
| Θ(n log n) sort, then a Θ(n) scan | Sum rule, larger term | Θ(n log n) |
| A loop that halves n each time, with Θ(1) work | Count iterations: log₂ n | Θ(log n) |
| A Θ(n) loop calling a Θ(log n) function | Product rule | Θ(n log n) |
Worked problem
Applying the rules
A program sorts n records in Θ(n log n) time, then for each record performs a binary search taking Θ(log n), then prints a summary in Θ(1). What is its total running time?
Show solution
- The searches cost n × Θ(log n) = Θ(n log n) by the product rule.
- Total = Θ(n log n) + Θ(n log n) + Θ(1).
- By the sum rule, the largest term dominates: Θ(n log n).
- Constant factors such as the 2 from two Θ(n log n) phases are dropped.
Answer: Θ(n log n). Doubling the work of the dominant phase changes the constant, not the growth rate.
What asymptotic notation does not say
A bound like \(\Theta(n \log n)\) hides two things you sometimes care about. It hides constant factors, which can make an asymptotically worse algorithm faster for every input you will actually see, as lesson 1's crossover example showed. And it says nothing about small inputs, below \(n_0\). Asymptotic notation also describes a function; you must still say which function: worst-case time, average-case time, best case, or memory. The sentence quicksort is \(O(n \log n)\) is false for the worst case and true for the average case, so a careful writer always names the case.
Common mistakes
MistakeWriting O(n²) when the worst-case running time is known to be Θ(n²).
InsteadUse Θ for a tight bound; O alone only says the function grows no faster, which also allows much smaller functions.
MistakeProving f ∈ O(g) by checking a few values of n.
InsteadGive explicit c and n₀ and prove the inequality for all n ≥ n₀; numerical checks only suggest constants.
MistakeSaying 3ⁿ is O(2ⁿ) because bases are constants.
InsteadExponential bases matter: 3ⁿ/2ⁿ = 1.5ⁿ is unbounded. Only logarithm bases can be ignored.
MistakeReading O(n²) = 3n² + 5n as a true equation.
InsteadThe equals sign means membership in a set of functions and only reads left to right.
MistakeStating a bound without saying which case it describes.
InsteadSay worst case, average case, best case or space; quicksort is Θ(n²) in the worst case and Θ(n log n) on average.
Summary and key terms
- f ∈ O(g) means f(n) ≤ c·g(n) for all n ≥ n₀, for some positive constants c and n₀.
- Ω gives a matching lower bound and Θ means both O and Ω hold, so the functions grow at the same rate.
- Little-o and little-omega express strict differences in growth; the limit of f/g classifies functions when it exists.
- Every logarithm grows more slowly than every positive power of n, and every polynomial more slowly than every exponential with base above 1.
- Logarithm bases do not matter inside asymptotic notation; exponential bases do.
- Bounds combine by rules: drop constants, keep the largest term of a sum, multiply nested costs.
- A bound must name its case, and it hides constant factors and behaviour on small inputs.
- Big-O
- An asymptotic upper bound: f ∈ O(g) if f(n) ≤ c·g(n) for all large n.
- Big-Omega
- An asymptotic lower bound: f ∈ Ω(g) if f(n) ≥ c·g(n) for all large n.
- Big-Theta
- A tight bound: f ∈ Θ(g) if f is both O(g) and Ω(g).
- Little-o
- f ∈ o(g) if f(n) < c·g(n) eventually for every c > 0; f grows strictly slower.
- Little-omega
- f ∈ ω(g) if g ∈ o(f); f grows strictly faster.
- Witness constants
- The specific c and n₀ that demonstrate a big-O or big-Omega bound.
- Limit test
- Classifying f against g by the limit of f(n)/g(n) as n grows.
- Lower-order term
- A term that grows more slowly than the dominant term and can be dropped asymptotically.
- Tight bound
- A bound that is both an upper and a lower bound, expressed with Θ.
- Stirling's approximation
- n! ≈ √(2πn)(n/e)ⁿ, which implies log(n!) ∈ Θ(n log n).
Practice problems
Work each problem on paper first, then reveal the answer to check.
Give constants c and n₀ proving 7n + 20 ∈ O(n).
Show answer
c = 27, n₀ = 1, since 20 ≤ 20n for n ≥ 1; or c = 8, n₀ = 20.
Is 2ⁿ⁺¹ in O(2ⁿ)? Justify.
Show answer
Yes: 2ⁿ⁺¹ = 2 · 2ⁿ, so c = 2 and n₀ = 1 work.
Is 2²ⁿ in O(2ⁿ)? Justify.
Show answer
No: 2²ⁿ/2ⁿ = 2ⁿ is unbounded, so no constant c works.
Order these by growth: n², n log n, √n, 2ⁿ, log n.
Show answer
log n, √n, n log n, n², 2ⁿ.
Use the limit test to compare n³ and 2ⁿ.
Show answer
n³/2ⁿ → 0, so n³ ∈ o(2ⁿ).
Prove n² ∉ O(n).
Show answer
If n² ≤ c·n for all n ≥ n₀ then n ≤ c for all n ≥ n₀, which fails at n = max(n₀, c + 1).
A Θ(n) loop runs inside a loop that halves a counter from n to 1. Bound the total.
Show answer
The outer loop runs Θ(log n) times, so Θ(n log n).
Is log₁₀ n in Θ(log₂ n)?
Show answer
Yes: they differ by the constant factor log₂ 10 ≈ 3.32.
Why is "quicksort is O(n log n)" ambiguous?
Show answer
It does not name the case; the worst case is Θ(n²) while the average case is Θ(n log n).
Show that n² + 100n ∈ Ω(n²).
Show answer
n² + 100n ≥ n² for all n ≥ 1, so c = 1 and n₀ = 1.
Lesson quiz · 5 questions
Score 60% or more to complete this lesson.
Create a free account to save your progress, take quizzes and earn a certificate.