Analysis of Algorithms

Big-O, Big-Omega, Big-Theta

CS205 Data Structures

Use arrow keys to navigate | Interactive slides ahead!

Why Analyze Algorithms?

The same problem can be solved in vastly different amounts of time depending on the algorithm.

Same problem, different speeds

Searching for a name in a phone book:

  • Linear scan: Check every page one by one
  • Binary search: Open to middle, eliminate half

Analogy

Sorting 1 million exam papers: a bad algorithm takes years, a good one finishes in seconds.

Impact at Scale

At n = 1,000 on a 1 GHz machine. Each bar = relative operation count (log scale).

Key Idea

Algorithm analysis lets us predict performance before we run the code, and compare algorithms independently of hardware.

What is an Algorithm?

An algorithm is a step-by-step procedure for solving a problem in a finite number of steps.

Properties of an Algorithm

  • Input: Zero or more inputs
  • Output: At least one output
  • Definiteness: Each step is precisely defined
  • Finiteness: Terminates after finite steps
  • Effectiveness: Each step is basic enough to carry out

Pseudocode Conventions

Algorithm findMax(A, n): Input: Array A of n integers Output: The maximum element currentMax ← A[0] for i ← 1 to n-1 do if A[i] > currentMax then currentMax ← A[i] return currentMax

Pseudocode is language-independent — the analysis works for any implementation.

Key Idea

We analyze the algorithm, not the program. The algorithm is the idea; the program is one implementation.

Measuring Efficiency

Why NOT wall-clock time?

  • Depends on computer hardware
  • Depends on programming language
  • Depends on compiler/interpreter
  • Depends on other processes running
  • Depends on specific input data

Warning

"My laptop ran it in 2 seconds" tells us almost nothing about the algorithm itself.

Machine-Independent Analysis

Instead of timing, we count primitive operations as a function of input size n.

Primitive Operations

  • Assigning a value to a variable
  • Comparing two values
  • Arithmetic operation (+, -, *, /)
  • Accessing an array element by index
  • Following a pointer / reference
  • Returning from a function

Each primitive operation takes constant time — O(1).

Counting Primitive Operations

Step through findMax and watch the operation counter. Click ▶ to advance.

Algorithm findMax(A, n): currentMax ← A[0] # 2 ops for i ← 1 to n-1 do if A[i] > currentMax # 2 ops currentMax ← A[i] # 2 ops return currentMax # 1 op

Array: [3, 7, 1, 9, 4]

Operations Count
0
currentMax
Click ▶ to start stepping through findMax.

Result

Best case: 3n ops. Worst case: 5n − 2 ops. Both are O(n) — the constants don't matter!

Growth Rate of Functions

Drag the slider to see how functions diverge as n grows. This is why Big-O matters!

Drop constants & lower-order terms

  • 5n + 3 → O(n)
  • 2n² + 10n + 7 → O(n²)
  • 100 → O(1)

Analogy

Driving 1000 miles? Starting 5 feet ahead doesn't matter. The dominant term determines everything at scale.

Big-O Notation (Upper Bound)

Definition

f(n) is O(g(n)) if there exist positive constants c and n₀ such that:

f(n) ≤ c · g(n) for all n ≥ n₀

Intuition

  • f(n) grows no faster than g(n)
  • g(n) is an upper bound on f(n)
  • "In the worst case, it's at most this"

Analogy

Big-O is like "this trip takes at most 3 hours." Might be less, never more.

Interactive: f(n) ≤ c·g(n)

f(n) = 3n+5 is O(n). Adjust c and n₀ to see valid bounds.

Big-O: Worked Examples

Example 1: Prove 3n + 5 is O(n)

We need: 3n + 5 ≤ c · n for all n ≥ n₀ Choose c = 4, n₀ = 5: 3n + 5 ≤ 4n ? 5 ≤ n ? (subtract 3n) Yes! When n ≥ 5 ✓ Check: n=5: 20 ≤ 20 ✓ n=10: 35 ≤ 40 ✓

∴ 3n + 5 is O(n) with c=4, n₀=5.

Example 2: Prove n² + 3n is O(n²)

We need: n² + 3n ≤ c · n² for all n ≥ n₀ Choose c = 2, n₀ = 3: n² + 3n ≤ 2n² ? 3n ≤ n² ? (subtract n²) 3 ≤ n ? (divide by n) Yes! When n ≥ 3 ✓ Check: n=3: 18 ≤ 18 ✓ n=5: 40 ≤ 50 ✓

∴ n² + 3n is O(n²) with c=2, n₀=3.

Warning

c and n₀ are not unique — many valid pairs exist. Also, 3n+5 is technically O(n²) too, but that's a loose (less useful) bound.

Big-Omega Ω (Lower Bound)

Definition

f(n) is Ω(g(n)) if there exist positive constants c and n₀ such that:

f(n) ≥ c · g(n) for all n ≥ n₀

Intuition

  • f(n) grows at least as fast as g(n)
  • g(n) is a lower bound on f(n)
  • "The algorithm needs at least this much time"

Example

3n + 5 is Ω(n): choose c=3, n₀=1. 3n+5 ≥ 3n ✓

Analogy

Ω is like "this trip takes at least 1 hour." Might take more, never less.

Visual: f(n) stays above c·g(n)

Big-Theta Θ (Tight Bound)

Definition

f(n) is Θ(g(n)) if there exist positive constants c₁, c₂, and n₀ such that:

c₁·g(n) ≤ f(n) ≤ c₂·g(n)

for all n ≥ n₀

Equivalently

f(n) is Θ(g(n)) iff f(n) is both O(g(n)) and Ω(g(n)).

Example

3n + 5 is Θ(n):

  • Upper: 3n+5 ≤ 4n for n ≥ 5 (c₂=4)
  • Lower: 3n+5 ≥ 3n for n ≥ 1 (c₁=3)

Visual: f(n) sandwiched

Key Idea

Θ gives the exact growth rate. When someone says "this is n log n," they really mean Θ(n log n).

Common Growth Rates

From fastest to slowest (best to worst for runtime):

NameBig-On = 10n = 100n = 1,000Example
ConstantO(1)111Array index
LogarithmicO(log n)3710Binary search
LinearO(n)101001,000Linear search
LinearithmicO(n log n)3070010,000Merge sort
QuadraticO(n²)10010,0001,000,000Bubble sort
CubicO(n³)1,00010⁶10⁹Matrix multiply
ExponentialO(2ⁿ)1,024~10³⁰~10³⁰¹Subsets
FactorialO(n!)3,628,800~10¹⁵⁸LOLPermutations

Warning

Anything beyond O(n²) becomes impractical quickly. O(2ⁿ) and O(n!) are unsolvable for n > 30–50.

Analyzing Loops

Single Loop → O(n)

for i ← 0 to n-1 do # runs n times doSomething() # O(1) each Total: n × O(1) = O(n)

Nested Loops → O(n²)

for i ← 0 to n-1 do # n times for j ← 0 to n-1 do # × n each doSomething() # O(1) Total: n × n × O(1) = O(n²)

Interactive: Watch the Loop Run

Iterations
0
Complexity

Key Idea

Multiply for nested loops. Halving/doubling = logarithmic. O(n) body inside O(n) loop = O(n²).

Analyzing Recursive Algorithms

Recursive algorithms use recurrence relations.

Pattern 1: Linear Recursion

T(n) = T(n-1) + O(1) → Total = n × c = O(n) Example: factorial(n)

Pattern 2: Divide & Conquer

T(n) = 2T(n/2) + O(n) → O(n log n) Example: Merge Sort

Pattern 3: Binary Search

T(n) = T(n/2) + O(1) → O(log n) Example: Binary Search

Recursion Tree: T(n) = 2T(n/2) + n

Key Idea

Write the recurrence → expand → find the pattern → determine total. log(n) levels × n cost per level = O(n log n).

Best Case, Worst Case, Average Case

Linear Search Example

Algorithm linearSearch(A, n, target): for i ← 0 to n-1 do if A[i] == target then return i return -1
CaseWhen?Comparisons
BestTarget is first1 = O(1)
WorstTarget last / absentn = O(n)
AverageEqually likely anywheren/2 = O(n)

Interactive: Try Different Inputs

Enter a value to search [2, 5, 7, 1, 9, 3, 8, 4]

Key Idea

We usually analyze worst case — it gives a guarantee on performance.

Warning

Don't confuse Big-O (bound type) with worst case (input type). You can say "the best case is O(1)."

Amortized Analysis (Brief Intro)

Sometimes a single operation is expensive, but averaged over many operations it's cheap.

ArrayList / Dynamic Array

When full, double capacity and copy everything.

Click to insert elements and watch the array grow.

Cost Analysis

Copies happen at insert 3, 5, 9, 17, ... Copy costs: 2 + 4 + 8 + 16 + ... ≤ 2n Total for n inserts = n + 2n = 3n Amortized cost/insert = 3n/n = 3 = O(1) !

Key Idea

Individual insert can be O(n). But amortized cost per insert is O(1). Expensive operations are rare enough to average out.

Analogy

Like saving a little each day, paying rent monthly. Average daily cost is still constant.

Space Complexity

Not just time — memory usage matters too.

What counts as space?

  • Input space: Memory for the input (usually excluded)
  • Auxiliary space: Extra memory beyond the input

In-Place Algorithms

Use only O(1) extra space.

temp ← A[i] # 1 extra variable A[i] ← A[j] A[j] ← temp # O(1) space

Space Complexity Examples

AlgorithmTimeSpace (aux)
Linear searchO(n)O(1)
Binary search (iterative)O(log n)O(1)
Binary search (recursive)O(log n)O(log n)*
Merge sortO(n log n)O(n)
Insertion sortO(n²)O(1)

* Recursive calls use stack space

Warning

Recursion uses stack space! Depth n = O(n) space even with no arrays.

Common Algorithm Complexities

Sorting

AlgorithmBestAverageWorst
Bubble SortO(n)O(n²)O(n²)
Insertion SortO(n)O(n²)O(n²)
Selection SortO(n²)O(n²)O(n²)
Merge SortO(n log n)O(n log n)O(n log n)
Quick SortO(n log n)O(n log n)O(n²)

Key Idea

Comparison-based sorting has a proven lower bound of Ω(n log n).

Searching

AlgorithmTimeRequires
Linear SearchO(n)Nothing
Binary SearchO(log n)Sorted array
Hash LookupO(1) avgHash table

Graph Algorithms (Preview)

AlgorithmTime
BFS / DFSO(V + E)
Dijkstra'sO((V+E) log V)

Common Pitfalls

Pitfall 1: Confusing O and Θ

"Linear search is O(n²)" is technically true but misleading. The tight bound is Θ(n). Always give the tightest bound.

Pitfall 2: Ignoring constants in practice

1,000,000·n vs n² — for n < 1,000,000 the "slower" n² is actually faster!

Pitfall 3: Log base doesn't matter

log₂(n) = log₁₀(n) × 3.32... The 3.32 is a constant → dropped in Big-O.

So O(log₂ n) = O(log₁₀ n) = O(ln n)

Pitfall 4: Confusing case & bound

Best/worst/average case = which input.
O / Ω / Θ = type of bound on the function.
These are separate concepts!

How to Determine Big-O Quickly

Rule 1: Drop Constants

5n + 100 → O(n) 3n² → O(n²) 42 → O(1)

Rule 2: Drop Lower-Order Terms

n² + n + 1 → O(n²) n³ + 100n² → O(n³) n log n + n → O(n log n)

Rule 3: Sequential = Add

O(n) + O(n) = O(n) O(n) + O(n²) = O(n²)

Rule 4: Nested = Multiply

O(n) × O(n) = O(n²) O(n) × O(log n) = O(n log n) for i to n: # O(n) for j to i: # avg n/2 ... # = O(n²) Sum: 0+1+...+n = n(n+1)/2 = O(n²)

Rule 5: Halving = log

Loop variable halved/doubled → O(log n) O(n) loop with O(log n) body → O(n log n)

Quick Flowchart

Single loop? → O(n) | Nested? → O(n²)
Halving? → O(log n) | Divide & conquer? → O(n log n)

Summary & Cheat Sheet

The Three Notations

NotationMeaningBound
O(g(n))f(n) ≤ c·g(n)Upper
Ω(g(n))f(n) ≥ c·g(n)Lower
Θ(g(n))c₁·g(n) ≤ f(n) ≤ c₂·g(n)Tight

Growth Rate Hierarchy

fastest slowest ←————————————————————————→ O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(n³) < O(2ⁿ) < O(n!)

Analysis Checklist

  • Identify primitive operations
  • Count ops as function of n
  • Drop constants & lower-order terms
  • Loops: add sequential, multiply nested
  • Recursion: write recurrence, solve
  • State which case (best/worst/avg)
  • Don't forget space complexity!

Key Takeaway

A good algorithm on slow hardware will always beat a bad algorithm on fast hardware — you just need large enough input.

Challenge: What's the Big-O?

Determine the time complexity of each code snippet.

Challenge: Rank from Fastest to Slowest

Click the complexities in order from fastest growing (best) to slowest growing (worst).

Your ranking (fastest → slowest):

Click complexities in order: fastest first.

Final Challenge: Quick-Fire Quiz

5 tricky questions about algorithm analysis. How many can you get?

Playground: Compare Growth Rates

Enter custom values of n and see how many operations each complexity class requires.

Try These

n = 10 → everything is fast. n = 1,000 → n² starts to hurt. n = 1,000,000 → only O(n log n) and below are practical!