1.1 Asymptotic Notation
Definition. f(n)=O(g(n)) if there exist constants c>0 and n0 such that f(n)≤c⋅g(n) for all n≥n0.
Definition. f(n)=Ω(g(n)) if there exist constants c>0 and n0 such that f(n)≥c⋅g(n) for all n≥n0.
Definition. f(n)=Θ(g(n)) if f(n)=O(g(n)) and f(n)=Ω(g(n)).
Definition. f(n)=o(g(n)) if limn→∞f(n)/g(n)=0.
Definition. f(n)=ω(g(n)) if limn→∞g(n)/f(n)=0.
Theorem 1.1. f(n)=O(g(n)) if and only if g(n)=Ω(f(n)).
Proof. Suppose f(n)=O(g(n)). Then there exist c,n0 such that f(n)≤c⋅g(n) for all n≥n0Hence g(n)≥(1/c)⋅f(n) for all n≥n0So g(n)=Ω(f(n)). The converse follows by symmetry. ■
Theorem 1.2. f(n)=Θ(g(n)) if and only if there exist constants c1,c2>0 and n0 such that c1⋅g(n)≤f(n)≤c2⋅g(n) for all n≥n0.
Proof. By definition, f(n)=Θ(g(n)) means f(n)=O(g(n)) and f(n)=Ω(g(n)). The former gives f(n)≤c2⋅g(n) for some c2>0And the latter gives f(n)≥c1⋅g(n) for some c1>0. Combining yields the stated inequality. ■
Theorem 1.3 (Limit Rule). If limn→∞f(n)/g(n)=c where 0<c<∞Then f(n)=Θ(g(n)). If c=0Then f(n)=O(g(n)). If c=∞Then g(n)=O(f(n)).
Proof. If c=0Then for any ε>0There exists n0 such that f(n)/g(n)<ε for all n≥n0So f(n)≤ε⋅g(n)Establishing f(n)=O(g(n)). If 0<c<∞Take ε=c/2; then (c/2)⋅g(n)≤f(n)≤(3c/2)⋅g(n) for sufficiently large nGiving Θ. The c=∞ case is symmetric. ■
Proposition 1.4. Asymptotic notation is transitive: if f=O(g) and g=O(h)Then f=O(h).
Proof. There exist c1,n1 with f(n)≤c1g(n) for n≥n1And c2,n2 with g(n)≤c2h(n) for n≥n2. Then f(n)≤c1c2h(n) for n≥max(n1,n2). ■
Proposition 1.5. O-notation is reflexive: f=O(f) for all f.
Proof. Take c=1 and n0=1. Then f(n)≤1⋅f(n) for all n≥1. ■
Worked Example: Proving $n^2 + 3n + 1 = O(n^2)$
We must find c>0 and n0 such that n2+3n+1≤c⋅n2 for all n≥n0.
Note that n2+3n+1≤n2+3n2+n2=5n2 for all n≥1 (since n≥1 implies 3n≤3n2 and 1≤n2).
So take c=5 and n0=1.
To show tightness, note n2+3n+1≥n2 for all n≥0So n2+3n+1=Θ(n2).
Worked Example: Proving $2^n \neq O(n^k)$ for any constant $k$
By the limit rule: limn→∞2n/nk=∞ for any fixed k (this follows from repeated application of L”Hôpital’s rule, or from the fact that log(2n)=nlog2 grows faster than log(nk)=klogn). Therefore 2n=ω(nk) for all kAnd in particular 2n=O(nk).
Worked Example: Sum of Harmonic Series and Its Use in Analysis
The k-th harmonic number is Hk=∑i=1k1/i. We show Hk=Θ(logk).
Upper bound: Hk=∑i=1k1/i≤1+∫1kx1dx=1+lnk=O(logk).
Lower bound: Hk≥∫1k+1x1dx=ln(k+1)=Ω(logk).
Therefore Hk=Θ(logk). This is used extensively in analysing algorithms like binary search on unbounded domains and the expected depth of elements in a random BST.
Worked Example: Analysing a Nested Loop Algorithm
Consider the algorithm:
The total number of iterations is ∑i=1n(n−i+1)=∑k=1nk=n(n+1)/2=Θ(n2).
Now consider:
The inner loop runs ⌊log2n⌋+1=O(logn) times, and the outer loop runs n times. Total: O(nlogn).
Now consider the triple loop:
Total iterations: ∑i=1n∑j=1ij=∑i=1n2i(i+1)=Θ(n3).
1.2 Common Complexity Classes
| Class | Name | Example |
|---|
| O(1) | Constant | Array access by index |
| O(logn) | Logarithmic | Binary search |
| O(n) | Linear | Linear search |
| O(nlogn) | Log-linear | Merge sort, heap sort |
| O(n2) | Quadratic | Bubble sort, insertion sort |
| O(n3) | Cubic | Naive matrix multiplication |
| O(2n) | Exponential | Naive Fibonacci, subset problems |
| O(n!) | Factorial | Generating all permutations |
Proposition 1.6. These classes form a strict hierarchy: O(1)⊊O(logn)⊊O(n)⊊O(nlogn)⊊O(n2)⊊O(n3)⊊O(2n)⊊O(n!)
1.3 Recurrences and the Master Theorem
Many divide-and-conquer algorithms yield recurrences of the form:
T(n)=aT(n/b)+f(n)
Where a≥1 is the number of subproblems, b>1 is the factor by which the input is divided, and f(n) is the cost of dividing and combining.
Theorem 1.7 (Master Theorem). Let a≥1 and b>1 be constants, let f(n) be a function, and let T(n) be defined on the nonnegative integers by the recurrence T(n)=aT(n/b)+f(n)Where we interpret n/b to mean either ⌊n/b⌋ or ⌈n/b⌉. Let c=logba. Then:
- If f(n)=O(nc−ε) for some ε>0Then T(n)=Θ(nc).
- If f(n)=Θ(nclogkn) for some k≥0Then T(n)=Θ(nclogk+1n).
- If f(n)=Ω(nc+ε) for some ε>0And if af(n/b)≤qf(n) for some constant q<1 and all sufficiently large n (the regularity condition), then T(n)=Θ(f(n)).
Proof (Case 1 sketch). The recursion tree has depth logbn. At level iThere are ai nodes, each costing f(n/bi). Since f(n)=O(nc−ε)The cost at level i is ai⋅(n/bi)c−ε=nc−ε⋅(a/bc−ε)i. The total cost is dominated by the leaves (level logbn), which contribute alogbn=nc. The internal levels contribute a geometric series with ratio a/bc−ε=bε>1So the leaf level dominates. ■
Worked Example: Merge Sort Recurrence
Merge sort divides into 2 subproblems of size n/2 and combines in O(n) time.
T(n)=2T(n/2)+Θ(n)
Here a=2, b=2So c=log22=1. We have f(n)=Θ(n)=Θ(nclog0n)Which is Case 2 with k=0.
Therefore T(n)=Θ(n1log1n)=Θ(nlogn).
Worked Example: Binary Search Recurrence
T(n)=T(n/2)+O(1)
Here a=1, b=2So c=log21=0. We have f(n)=O(1)=O(n0). This matches Case 2 with k=0.
Therefore T(n)=Θ(logn).
Worked Example: Strassen's Matrix Multiplication
Strassen’s algorithm divides into 7 subproblems of size n/2 and combines in O(n2) time.
T(n)=7T(n/2)+O(n2)
Here a=7, b=2So c=log27≈2.807. We have f(n)=O(n2)=O(nc−ε) with ε=c−2≈0.807Which is Case 1.
Therefore T(n)=Θ(nlog27)=Θ(n2.807).
1.4 Amortised Analysis
Amortised analysis gives the average cost per operation over a sequence of operations, even when Individual operations may be expensive.
Methods:
- Aggregate analysis: Compute total cost of n operations, divide by n.
- Accounting method: Assign an amortised cost to each operation; the sum of amortised costs must be an upper bound on the total actual cost.
- Potential method: Define a potential function Φ; the amortised cost of the i-th operation is c^i=ci+Φ(Di)−Φ(Di−1).
Theorem 1.8 (Potential Method). If Φ(Di)≥Φ(D0) for all i≥1Then the total amortised cost ∑i=1nc^i is an upper bound on the total actual cost ∑i=1nci.
Proof. ∑i=1nc^i=∑i=1n(ci+Φ(Di)−Φ(Di−1))=∑i=1nci+Φ(Dn)−Φ(D0)≥∑i=1nci. ■
Example (Dynamic Array). A dynamic array doubles in size when full. Insertion is O(1) Amortised: most insertions cost O(1); occasional resizing costs O(n)But is paid for by the Surplus from previous O(1) insertions.
Worked Example: Dynamic Array Amortised Analysis (Accounting Method)
Assign an amortised cost of 3 per insertion. The actual cost of insertion without resizing is 1. We store the surplus of 2 as “credit” on each element in the array.
When the array of capacity k is full and we must resize to capacity 2k:
- The resizing cost is k (copy all k elements).
- We have 2k credits stored (2 per element).
- We spend k credits on the copy, leaving k credits.
- The new array has k elements, so we need 2k more credits, but we already have k.
- Subsequent insertions build up credits again.
Since the total credits are always non-negative, the amortised cost of 3 per insertion covers all actual costs.
Worked Example: Dynamic Array Amortised Analysis (Potential Method)
Define the potential function Φ(D)=2n−m where n is the number of elements and m is the capacity. We require m≥nSo Φ≥0 (since 2n−n=n≥0).
Case 1: No resize. c^=1+(2(n+1)−m)−(2n−m)=1+2=3.
Case 2: Resize from m=n to m=2n. c^=(1+n)+(2(n+1)−2n)−(2n−n)=(n+1)+2−n=3.
In both cases, the amortised cost is O(1).
:::caution Common Pitfall Amortised analysis gives a guarantee over a sequence of operations, not a per-operation worst-case bound. A single operation can still be expensive (e.g., a resize in a dynamic array costs O(n)). Amortised bounds are meaningful only when the sequence length is not bounded by a constant.
:::