Analysis of Algorithms

Big-O, Big-Omega, Big-Theta

CS205 Data Structures

_____ _ _____ _ _ _ |_ _(_)_ __ ___ ___ / ____|___ _ __ ___ _ __| | _____ _(_) |_ _ _ | | | | '_ ` _ \ / _ \ | / _ \| '_ ` _ \| '_ \ |/ _ \ \/ / | __| | | | | | | | | | | | | __/ |__| (_) | | | | | | |_) | | __/> <| | |_| |_| | |_| |_|_| |_| |_|\___|\____\___/|_| |_| |_| .__/|_|\___/_/\_\_|\__|\__, | |_| |___/

Use arrow keys or buttons to navigate | Press S to reveal steps

1 / 20

Why Analyze Algorithms?

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

Same problem, different speeds

Consider searching for a name in a phone book:

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

Analogy

Imagine sorting 1 million exam papers. A bad algorithm could take years. A good one finishes in seconds.

Impact at scale (n = 1,000)

ComplexityOperationsAt 1 GHz
O(n)1,0001 μs
O(n log n)~10,00010 μs
O(n²)1,000,0001 ms
O(n³)10&sup9;1 sec
O(2ⁿ)~10³&sup0;¹Heat death of universe

Key Idea

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

2 / 20

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 be carried 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

We use pseudocode (not any specific language) so the analysis is language-independent.

Key Idea

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

3 / 20

Measuring Efficiency

Why NOT wall-clock time?

  • Depends on the specific computer hardware
  • Depends on the programming language used
  • Depends on the compiler/interpreter
  • Depends on other processes running
  • Depends on the 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 — we call it O(1).

4 / 20

Counting Primitive Operations

Let's carefully count every operation in findMax:

Algorithm findMax(A, n): currentMax ← A[0] # 2 ops # (index + assign) for i ← 1 to n-1 do # 1 assign + (n-1) compares # + (n-1) increments if A[i] > currentMax # 2(n-1) ops then # (index + compare) currentMax ← A[i] # 0 to 2(n-1) ops # (index + assign) return currentMax # 1 op

Counting it up

OperationCount
Initialization2
Loop control1 + 2(n-1)
Comparison in loop2(n-1)
Assignment in loop (worst)2(n-1)
Return1
Best case total2 + 3(n-1) + 1 = 3n
Worst case total2 + 5(n-1) + 1 = 5n - 2

Key Idea

Both best and worst case are proportional to n. The exact constants (3 vs 5) don't matter for classification — it's O(n) either way.

5 / 20

Growth Rate of Functions

We care about how running time grows as input size n increases, not the exact count.

Drop constants & lower-order terms

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

Analogy

If you're driving 1000 miles, it doesn't matter if you start 5 feet ahead. At large scale, the dominant term determines everything.

Growth curves (ASCII)

time ^ | * 2^n | * | * | * . n^2 | * . . | * . . | * . . | * . . _--- n log n | * . . _---- | * . . __---- ___--- n | * . . __---- ___--- |*. . ---- ___----- |..----___---- __---- log n +--------------------------------> n |

Key Idea

Growth rate analysis lets us focus on the big picture — how algorithms scale, not how they perform on tiny inputs.

6 / 20

Big-O Notation (Upper Bound)

Formal Definition

Definition

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

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

What it means intuitively

  • 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 saying "this trip will take at most 3 hours." It might take less, but never more (past a certain point).

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

^ | c*g(n) | / | / | / | / f(n) | / ../ | / ./ | /./ | /. | /. | * ← n&sub0; (from here on, | /| f(n) ≤ c*g(n)) |/ | +--|------------------> n | n&sub0; After n&sub0;, f(n) is always at or below the line c*g(n).
7 / 20

Big-O: Worked Examples

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

We need to find c and n&sub0; such that: 3n + 5 ≤ c · n for all n ≥ n&sub0; Choose c = 4, n&sub0; = 5: 3n + 5 ≤ 4n ? 5 ≤ n ? (subtract 3n) Yes! When n ≥ 5 ✓ Check: n=5: 3(5)+5 = 20 ≤ 4(5) = 20 ✓ n=6: 3(6)+5 = 23 ≤ 4(6) = 24 ✓ n=10: 3(10)+5= 35 ≤ 4(10)= 40 ✓

Therefore, 3n + 5 is O(n) with c=4, n0=5.

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

We need to find c and n&sub0; such that: n² + 3n ≤ c · n² for all n ≥ n&sub0; Choose c = 2, n&sub0; = 3: n² + 3n ≤ 2n² ? 3n ≤ n² ? (subtract n²) 3 ≤ n ? (divide by n) Yes! When n ≥ 3 ✓ Check: n=3: 9+9 = 18 ≤ 2(9) = 18 ✓ n=4: 16+12= 28 ≤ 2(16)= 32 ✓ n=5: 25+15= 40 ≤ 2(25)= 50 ✓

Therefore, n² + 3n is O(n²) with c=2, n0=3.

Warning

The choice of c and n0 is not unique — many valid pairs exist. You only need to find one pair that works. Also, 3n + 5 is technically O(n²) too, but that's a loose (less useful) bound.

8 / 20

Big-Omega Ω (Lower Bound)

Definition

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

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

What it means

  • 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, n0=1.

3n + 5 ≥ 3n for all n ≥ 1. ✓

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

^ | f(n) | / | / | / | / | / . c*g(n) | / . | /. | /. | * ← n&sub0; | /| |. | +--|------------------> n | n&sub0; After n&sub0;, f(n) is always at or above the line c*g(n).

Analogy

Big-Omega is like saying "this trip takes at least 1 hour." It might take more, but never less.

9 / 20

Big-Theta Θ (Tight Bound)

Definition

f(n) is Θ(g(n)) if there exist positive constants c1, c2, and n0 such that:

c1·g(n) ≤ f(n) ≤ c2·g(n)

for all n ≥ n0

Equivalently

f(n) is Θ(g(n)) if and only if:

  • f(n) is O(g(n)) — and
  • f(n) is Ω(g(n))

Example

3n + 5 is Θ(n):

  • Upper: 3n+5 ≤ 4n for n ≥ 5 (c2=4)
  • Lower: 3n+5 ≥ 3n for n ≥ 1 (c1=3)
  • Use n0 = 5, c1=3, c2=4 ✓

Visual: f(n) sandwiched

^ | c&sub2;*g(n) | / | / f(n) | / ../ | / ./ c&sub1;*g(n) | /./ . | /. . | /. . | * . | /. |/. +-------------------> n | n&sub0; f(n) is "sandwiched" between c&sub1;*g(n) and c&sub2;*g(n).

Key Idea

Θ gives the exact growth rate. When someone says "this algorithm is n log n," they really mean Θ(n log n). It's the most precise characterization.

10 / 20

Common Growth Rates

Listed from fastest to slowest growing (best to worst for runtime):

Name Big-O n = 10 n = 100 n = 1,000 Example
ConstantO(1) 111 Array index
LogarithmicO(log n) 3710 Binary search
LinearO(n) 101001,000 Linear search
LinearithmicO(n log n) 3070010,000 Merge sort
QuadraticO(n²) 10010,0001,000,000 Bubble sort
CubicO(n³) 1,0001,000,00010&sup9; Matrix multiply
ExponentialO(2ⁿ) 1,024~10³&sup0;~10³&sup0;¹ Subsets
FactorialO(n!) 3,628,800~10¹&sup5;&sup8;LOL Permutations

Warning

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

11 / 20

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 times each doSomething() # O(1) Total: n × n × O(1) = O(n²)

Loop with Halving → O(log n)

i ← n while i > 0 do doSomething() # O(1) i ← i / 2 How many times does i halve before reaching 0? n → n/2 → n/4 → ... → 1 That's log&sub2;(n) steps. Total: O(log n)

Loop with Doubling → O(log n)

i ← 1 while i < n do doSomething() # O(1) i ← i * 2 1 → 2 → 4 → 8 → ... → n Also log&sub2;(n) steps. O(log n)

Key Idea

Multiply for nested loops. Halving or doubling the loop variable means logarithmic. A loop running n times with a O(log n) body gives O(n log n).

12 / 20

Analyzing Recursive Algorithms

Recursive algorithms are analyzed using recurrence relations.

Pattern 1: Linear Recursion

T(n) = T(n-1) + O(1) Expand: T(n) = T(n-1) + c T(n-1) = T(n-2) + c T(n-2) = T(n-3) + c ... T(1) = c Total = n × c = O(n) Example: factorial(n) fact(n) = n * fact(n-1) fact(1) = 1

Pattern 2: Two calls, halved

T(n) = 2T(n/2) + O(n) This is Merge Sort! Solves to: O(n log n) (Via the Master Theorem or by expanding the tree)

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

n ← cost: n / \ n/2 n/2 ← cost: n / \ / \ n/4 n/4 n/4 n/4 ← cost: n ... ... ... ... 1 1 1 ... 1 1 ← cost: n log(n) levels Each level costs n Total: n × log(n) = O(n log n)

Pattern 3: Single call, halved

T(n) = T(n/2) + O(1) This is Binary Search! log(n) levels, O(1) per level. Total: O(log n)

Key Idea

Write the recurrence → expand it → find the pattern → determine the total.

13 / 20

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 is last / absentn = O(n)
AverageTarget equally likely anywheren/2 = O(n)
Best case: found immediately! [ target | ... | ... | ... ] ^ found at index 0 Worst case: check everything [ ... | ... | ... | target ] ^ found at index n-1 (or not found)

Key Idea

We usually analyze the worst case because:

  • It gives a guarantee on performance
  • It's often easier to analyze than average case
  • For some algorithms, the worst case occurs often

Warning

Don't confuse Big-O (a bound on a function) with worst case (a type of input). You can talk about the Big-O of the best case, worst case, or average case.

14 / 20

Amortized Analysis (Brief Intro)

Sometimes a single operation is expensive, but averaged over many operations, the cost is low.

ArrayList / Dynamic Array

When the array is full, we double its capacity and copy everything.

Insert #1: [1|_] cost: 1 Insert #2: [1|2] cost: 1 Insert #3: [1|2|3|_] cost: 1 + 2 (copy) Insert #4: [1|2|3|4] cost: 1 Insert #5: [1|2|3|4|5|_|_|_] cost: 1 + 4 (copy) Insert #6: [1|2|3|4|5|6|_|_] cost: 1 Insert #7: [1|2|3|4|5|6|7|_] cost: 1 Insert #8: [1|2|3|4|5|6|7|8] cost: 1

Cost analysis for n inserts

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

Key Idea

Individual insert can be O(n) in the worst case. But the amortized cost per insert is O(1). "Expensive operations are rare enough that they average out."

Analogy

Think of it like paying rent. You save a little each day, and once a month you pay a big lump sum. On average, your daily spending is still constant.

15 / 20

Space Complexity

Algorithm analysis isn't just about timememory usage matters too.

What counts as space?

  • Input space: Memory for the input itself (usually excluded from analysis)
  • Auxiliary space: Extra memory the algorithm needs beyond the input

In-Place Algorithms

Use only O(1) extra space — they modify the input directly.

Swap two elements: O(1) extra temp ← A[i] # 1 variable A[i] ← A[j] A[j] ← temp

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)
Copy arrayO(n)O(n)

* Recursive calls use stack space

Warning

Recursion uses stack space! Each recursive call adds a frame. A recursion depth of n means O(n) space, even if no arrays are allocated.

16 / 20

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). No comparison sort can do better in the worst case.

Searching

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

Graph Algorithms (Preview)

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

V = vertices, E = edges. You'll learn these later in the course!

17 / 20

Common Pitfalls

Pitfall 1: Confusing O and Θ

Saying "linear search is O(n²)" is technically true (O is an upper bound), but misleading. The tight bound is Θ(n). Always give the tightest bound you can.

Pitfall 2: Ignoring constants in practice

Big-O ignores constants, but in the real world, an O(n) algorithm with a huge constant can be slower than O(n²) for reasonable inputs.

1,000,000 · n vs n² For n < 1,000,000: the "slower" n² is actually faster!

Pitfall 3: Log base doesn't matter

In Big-O, log base is irrelevant because:

log&sub2;(n) = log&sub1;&sub0;(n) / log&sub1;&sub0;(2) = log&sub1;&sub0;(n) × 3.32... The 3.32 is just a constant, and constants are dropped in Big-O. So O(log&sub2; n) = O(log&sub1;&sub0; n) = O(ln n)

Pitfall 4: Confusing case & bound

Best/worst/average case = which input.
O / Ω / Θ = type of bound on the function.

You can say: "The worst case is Θ(n²)" or "The best case is O(n)." These are separate concepts!

18 / 20

How to Determine Big-O Quickly

Rules of thumb for fast analysis:

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

for i ← 0 to n: # O(n) ... for j ← 0 to n: # O(n) ... Total: O(n) + O(n) = O(n)

Rule 4: Nested = Multiply

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

Rule 5: Halving = log

If loop variable is halved or doubled each iteration → O(log n) If O(n) loop contains O(log n) work: → O(n log n)

Quick Decision Flowchart

Single loop over n? → O(n)
Nested loop? → O(n²)
Halving? → O(log n)
Divide and conquer? → Probably O(n log n)

19 / 20

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&sub1;·g(n) ≤ f(n) ≤ c&sub2;·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 operations as a function of n
  • Drop constants and lower-order terms
  • For loops: add sequential, multiply nested
  • For recursion: write recurrence, solve it
  • State which case (best/worst/average)
  • Don't forget space complexity!

Key Takeaway

Algorithm analysis is about understanding scalability. A good algorithm on slow hardware will always eventually beat a bad algorithm on fast hardware — you just need a large enough input.

"A good algorithm is worth a thousand fast processors."
20 / 20