Algorithm analysis measures how an algorithm’s resource requirements grow with input size, independent of hardware. Big-O notation provides an upper bound on growth rate, like saying a car’s top speed is at most 120 mph regardless of road conditions. The limit rule gives a practical shortcut: compare growth rates by taking the ratio as the input grows. Asymptotic notation is transitive because bounding one function by another creates a chain of bounds. The key insight is that constants and lower-order terms become irrelevant for large inputs.
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≥n0 So 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>0 And 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=0 Then f(n)=O(g(n)). If c=∞ Then g(n)=O(f(n)).
Proof. If c=0 Then for any ε>0There exists n0 such that f(n)/g(n)<ε for all n≥n0 So 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≥n1 And 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≥0 So 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 k And 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).
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:
for i = 1 to n:
for j = i to n:
constant-time work
The total number of iterations is ∑i=1n(n−i+1)=∑k=1nk=n(n+1)/2=Θ(n2).
Now consider:
for i = 1 to n:
j = 1
while j < n:
j = j * 2
constant-time work
The inner loop runs ⌊log2n⌋+1=O(logn) times, and the outer loop runs n times. Total: O(nlogn).
Now consider the triple loop:
for i = 1 to n:
for j = 1 to i:
for k = 1 to j:
constant-time work
Total iterations: ∑i=1n∑j=1ij=∑i=1n2i(i+1)=Θ(n3).
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 ε>0 Then T(n)=Θ(nc).
If f(n)=Θ(nclogkn) for some k≥0 Then T(n)=Θ(nclogk+1n).
If f(n)=Ω(nc+ε) for some ε>0 And 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ε>1 So 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=2 So 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=2 So 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=2 So c=log27≈2.807. We have f(n)=O(n2)=O(nc−ε) with ε=c−2≈0.807Which is Case 1.
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≥1 Then the total amortised cost ∑i=1nc^i is an upper bound on the total actual cost ∑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≥n So Φ≥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.