L
LLLOS.ai
Learn
L

Chapter 9 — Complexity and Big O notation

Class 12 · Computer Science

Overview

This unit introduces complexity and Big O notation, the language used to describe how algorithms behave as input size grows. Students learn to analyse time and space requirements of algorithms, compare different approaches, and classify algorithms by their efficiency. The unit covers precise definitions of asymptotic notations (Big O, Theta, Omega), methods for counting basic operations, the significance of worst-case, average-case and best-case analyses, and common complexity classes such as constant, logarithmic, linear, quadratic and exponential. It also teaches techniques for simplifying expressions, using limits and dominant-term reasoning, and analysing common algorithms such as searching, sorting and simple recursive procedures. Understanding complexity helps in choosing suitable algorithms and data structures for real problems, estimating performance, and writing programs that scale. For ISC students, this knowledge is crucial for designing efficient solutions under resource constraints and for answering board-style questions that request formal proofs or comparisons of running times.

Learning Objectives

  • Explain the purpose of algorithmic complexity analysis and why it matters for large inputs.
  • Define Big O, Big Theta and Big Omega notation and use them to classify functions.
  • Estimate time complexity by counting basic operations and using dominant-term simplification.
  • Differentiate between worst-case, average-case and best-case complexity and give examples.
  • Analyse iterative and recursive algorithms and compute their time and space complexities.
  • Compare complexity classes with examples of algorithms that belong to each class.
  • Apply techniques such as substitution and recursion trees to solve recurrence relations.
  • Choose appropriate algorithms based on time and space trade-offs for given problems.

Topics in this chapter

12 topics · tap a topic title to jump straight to it.

💻1

Introduction to algorithmic complexity

What we mean by algorithmic complexity
This topic explains the purpose of analysing algorithms. Algorithmic complexity is about measuring how resources—mainly time and memory—grow as the input size increases. Instead of measuring exact running time in seconds, which depends on the machine and programming language, we describe growth using functions of n, the input size. This abstract view helps compare algorithms independent of implementation details.

Inputs, operations and models
To study time complexity we pick a model of computation where basic operations (assignments, comparisons, arithmetic) take constant time. Input size n should be defined clearly: number of elements in an array, number of bits in a number, number of vertices in a graph, etc. For space complexity, count additional memory required excluding space for input unless stated otherwise.

Counting basic operations
Start by identifying the fundamental operation for the problem — for search it may be a comparison, for arithmetic it may be an addition. Count how many times that operation executes as a function of n. For loops, multiply iterations by work per iteration; for nested loops add up the contributions precisely when needed, or use dominant-term reasoning for asymptotic results.

Why this matters
When n is small, many algorithms perform well. For large n, algorithms with lower growth rates (for example, linear instead of quadratic) can be the difference between feasible and infeasible solutions. Complexity analysis helps software engineers predict scalability, choose data structures, and meet performance requirements.

Practical approach
When learning, practise on simple code fragments: single loops, nested loops, recursive functions. Translate each into an operation count, simplify by keeping the highest-order term and dropping constants, and express the result using asymptotic notation. Always state assumptions: what n is, which operations count as unit cost, and whether you measure worst-case, average-case or best-case.

📌 Examples
  • Finding maximum in array of size n: compare each element once → ≈ n comparisons → Θ(n).
  • Summing elements: one addition per element → n additions → Θ(n).
  • Copying an array: n assignments → Θ(n).
  • Accessing A[k] by index in array → constant time → Θ(1).
🧮 Formulas
  1. Operation count as function f(n) expressed in O-notation
  2. Dominant term rule: for polynomial, highest degree term determines growth
📊 Visual ideas
Plot comparing constant, logarithmic, linear and quadratic curves to see relative growth
Bar diagram showing individual contributions from separate loops and the dominant term
💻2

Big O notation: formal definition and practice

Formal definition and intuition
Big O notation captures an upper bound on the growth of a function. We write f(n) = O(g(n)) to mean there exist positive constants c and n0 such that for every n ≥ n0, f(n) ≤ c·g(n). Intuitively, beyond some threshold n0, f(n) is no larger than a constant multiple of g(n). This is useful to express worst-case running time up to constant factors and ignoring smaller terms.

Choosing g(n)
When you analyse an algorithm, choose g(n) to be a simple function that reflects the dominant growth: common choices are 1, log n, n, n log n, n^2, 2^n and so on. The goal is not to find the tightest possible g(n) for every exercise but to express complexity in a class that shows how the algorithm scales. If asked for a tight bound, provide Θ instead.

Proving Big O claims
To prove f(n) = O(g(n)) using the formal definition, explicitly give values for c and n0 and show the inequality holds. For polynomial expressions, you can often bound lower-degree terms by the leading term when n is large. For example, f(n)=3n^2+5n+20 ≤ 3n^2+5n^2+20n^2 = 28n^2 for n ≥ 1, so choose c=28 and n0=1. State these choices in answers to be clear and rigorous.

Common proof techniques
Use simple inequalities like n ≤ n^2 for n ≥ 1, or log n ≤ n for n ≥ 2. For products like n log n, factor n and use properties of logarithms. When comparing functions whose forms are not immediately obvious, use limits: if lim_{n→∞} f(n)/g(n) = L where L is finite, then f(n)=O(g(n)).

Practical examples and warnings
Examples: 7n+10 = O(n); log n = O(n); n = O(n log n) but not vice versa. Beware of incorrect statements like 2^n = O(n^k) for fixed k — exponentials dominate polynomials. When writing solutions, always indicate the assumed domain (usually integers n ≥ 1) and give the constants used for the formal definition to earn full marks in examinations.

📌 Examples
  • Prove 5n+10 = O(n) by choosing c = 6 and n0 = 10 so that for n ≥ n0, 5n+10 ≤ 6n.
  • Show 3n^2+2n = O(n^2) by selecting c = 5 and n0 = 1.
  • Demonstrate log n = O(n) because for n ≥ 2, log n ≤ n.
  • Explain why 2^n is not O(n^k) for any fixed k by growth comparison.
🧮 Formulas
  1. Definition: f(n) = O(g(n)) if ∃c>0, n0 s.t. ∀n≥n0, f(n) ≤ c·g(n).
  2. When simplifying, drop constants and lower-order terms for asymptotic class.
📊 Visual ideas
Graph showing f(n) and c·g(n) lines with a crossing point at n0 beyond which f(n) ≤ c·g(n).
Sketch comparing polynomial and exponential growth curves to see failure of polynomial bound.
💻3

Big Omega and Big Theta: lower bounds and tight bounds

Big Omega explained
Big Omega notation provides a lower bound on growth. Writing f(n) = Ω(g(n)) means that for sufficiently large n, f(n) is at least a constant multiple of g(n). Formally, there exist positive constants c and n0 such that for every n ≥ n0, f(n) ≥ c·g(n). This notation is used to state that the algorithm must use at least that much resource on inputs of size n, often representing best-case behaviour or guaranteed cost.

Big Theta explained
Big Theta is used when we can bound a function both above and below by the same simple function, up to constants. f(n) = Θ(g(n)) means there exist positive constants c1, c2 and n0 such that c1·g(n) ≤ f(n) ≤ c2·g(n) for all n ≥ n0. Θ gives a tight asymptotic characterisation: g(n) captures the growth rate of f(n) within constant factors.

Why both notations matter
O alone gives an upper bound but may be loose; Ω alone gives a lower bound but might be weak. Θ is strongest as it says the two functions are of the same order. When analysing algorithms, Θ is preferred if achievable because it gives precise asymptotic growth. For algorithm descriptions, O is common to state worst-case guarantees, while Ω is used for best-case results.

Techniques to prove Ω and Θ
To show Ω, find c and n0 such that f(n) ≥ c·g(n) holds. Often choose c as the leading coefficient divided by two and pick n0 so lower-order terms are controlled. For Θ, prove both O and Ω separately. Limit comparisons can help: if lim_{n→∞} f(n)/g(n) = L where 0 < L < ∞, then f(n) = Θ(g(n)).

Examples, interpretation and exam tips
3n+4 = Θ(n) since it is both O(n) and Ω(n). Summing an array always does Θ(n) work. Searching an unordered list has best-case Ω(1) if found immediately and worst-case O(n) if not found; therefore you can state both bounds but not Θ(n) without specifying average-case. In exams, explicitly give constants and n0 for formal proofs and explain the meaning of the bound in terms of best/worst/guaranteed behaviour.

📌 Examples
  • 3n+4 = Ω(n) with c = 3 and n0 = 1; also O(n) with c = 7 ⇒ Θ(n).
  • Sum of array always requires n-1 additions ⇒ Ω(n) and O(n) so Θ(n).
  • Search in unordered list: best-case Ω(1), worst-case O(n); not Θ(n) unless average-case considered.
  • Polynomial p(n)=an^k+... is Ω(n^k) by taking c=a/2 for large n where lower terms are smaller.
🧮 Formulas
  1. f(n) = Ω(g(n)) if ∃c>0, n0 such that ∀n≥n0, f(n) ≥ c·g(n).
  2. f(n) = Θ(g(n)) iff f(n) = O(g(n)) and f(n) = Ω(g(n)).
📊 Visual ideas
Graph showing f(n) sandwiched between c1·g(n) and c2·g(n) beyond n0.
Comparison of best-case and worst-case lines for a search operation.
💻4

Common complexity classes and their meaning

Overview and why classes are used
Complexity classes let us group algorithms by growth behaviour so we can compare and choose among them. Students should memorise and understand the practical meaning of common classes: O(1) (constant), O(log n) (logarithmic), O(n) (linear), O(n log n) (linearithmic), O(n^2) (quadratic), O(n^k) (polynomial), and O(2^n) / O(n!) (exponential/factorial). Knowing where an algorithm falls quickly shows whether it can handle large inputs.

Constant time O(1)
Constant-time operations do the same amount of work regardless of n. Examples include accessing an array cell by index, returning a fixed value, or computing a simple arithmetic expression. In code, statements outside loops or fixed-number operations are constant.

Logarithmic O(log n)
Logarithmic time appears when each step reduces the problem multiplicatively. Binary search halves the range each comparison and needs roughly log_2 n comparisons. Balanced search trees and many heap operations have logarithmic time because they move along tree height that grows logarithmically with n.

Linear and linearithmic
Linear algorithms perform work proportional to n, such as a single pass over an array. Linearithmic O(n log n) commonly arises in efficient sorting algorithms: the algorithm performs log n levels of division and does linear work at each level, resulting in n log n total cost. Examples include mergesort and heapsort.

Quadratic and polynomial
Nested loops typically produce polynomial times. Two nested loops give O(n^2), three nested loops O(n^3), and so on. These are acceptable for small n but quickly become costly. Many naive algorithms for pairwise comparisons or matrix operations fall in this category.

Exponential and practical limits
Exponential time, like O(2^n), grows extremely fast and becomes unusable for moderate n. Brute-force search over subsets or permutations often leads to exponential or factorial time. Such algorithms are studied for small n or as theoretical baselines; practical solutions require heuristics, approximation, or special structure to avoid exponential blow-up.

Guidance
Prefer lower asymptotic classes for large-scale problems, but consider constants, memory and implementation complexity. For small inputs, a simpler higher-class algorithm with a tiny constant may be acceptable. Always justify choices based on both asymptotic and practical considerations.

📌 Examples
  • O(1): A[k] access by index.
  • O(log n): Binary search on sorted array.
  • O(n): Single loop summing array elements.
  • O(n^2): Two nested loops comparing all pairs (e.g., simple bubble sort).
🧮 Formulas
  1. Hierarchy example: 1 << log n << n << n log n << n^2 << 2^n (growth order).
  2. Counting rule: nested loops multiply iteration counts to get polynomial degree.
📊 Visual ideas
Plot comparing curves for 1, log n, n, n log n, n^2 and 2^n to visualise separation.
Diagram of binary search halving intervals each step.
🔢5

Counting operations in loops and sequences

Start with the basic operation
When analysing loops pick a basic operation to count: a comparison, assignment, swap, arithmetic step, or function call. The choice depends on the algorithm. Make it explicit in your solution. The total time is the number of times this operation executes as a function of n, multiplied by the assumed unit cost for each operation.

Single loops
For a simple loop that runs from 1 to n performing constant work, the total cost is c·n for some constant c. In asymptotic terms, this is Θ(n). If the loop bounds are functions of n (like from 1 to 2n), adjust counts accordingly; e.g., 2n iterations are still Θ(n).

Nested loops and sums
For nested loops multiply the iteration counts when inner and outer bounds are independent: two loops each from 1 to n produce n^2 iterations. When inner loop depends on outer variable, convert to a sum — for example nested loops where inner runs up to i produce Σ_{i=1}^n i = n(n+1)/2 = Θ(n^2). Use known closed forms for arithmetic and geometric series to simplify counts precisely when required.

Triangular and irregular patterns
Triangular patterns arise often in algorithms comparing pairs: sum of 1 to n gives Θ(n^2). For loops with varying increments consider their nature: arithmetic increment by constant yields Θ(n) iterations, multiplicative increments (i*=2) yield Θ(log n) iterations. For two-level variable bounds, carefully set up double sums or change the order of summation if that simplifies evaluation.

Multiple sequential pieces
If code has several segments executed in sequence, add their costs. The overall asymptotic complexity is determined by the dominant term. For example, a Θ(n^2) block followed by Θ(n) results in Θ(n^2) overall. Make this explicit so examiners see your reasoning for dropping lower-order terms and constants.

Edge cases and exact counts
When asked for exact operation counts provide the sum expression and its closed form before simplifying to Θ. For example, for the nested loop with inner limit i, show Σ_{i=1}^n i = n(n+1)/2, then state Θ(n^2). Showing both exact and asymptotic forms earns full credit in formal answers.

📌 Examples
  • Single loop: for(i=1;i<=n;i++) does n iterations → Θ(n).
  • Nested fixed loops: for(i=1;i<=n;i++) for(j=1;j<=n;j++) does n^2 iterations → Θ(n^2).
  • Triangular nested loop: for(i=1;i<=n;i++) for(j=1;j<=i;j++) does n(n+1)/2 → Θ(n^2).
  • Loop with doubling: for(i=1;i<n;i*=2) runs ≈ log_2 n iterations → Θ(log n).
🧮 Formulas
  1. \[Sum_{i=1}^n i = n(n+1)/2 = Θ(n^2)\]
  2. Arithmetic series and geometric series formulas used in iteration counting
📊 Visual ideas
Bar chart showing contributions of inner and outer loops and dominant term.
Plot showing iteration counts for linear vs logarithmic loops.
💻6

Logarithms and binary algorithms in detail

Logarithm concepts
Understand that log_b n is the exponent to which base b must be raised to obtain n. In complexity, the base b is not important because logs with different fixed bases differ only by a constant factor: log_a n = log_b n / log_b a. Asymptotic notation ignores constant factors, so any logarithm is Θ(log n) regardless of base. Logarithms appear when problem size is reduced multiplicatively each step.

Binary search mechanics
Binary search is the classic logarithmic-time algorithm. Given a sorted array of size n, we compare the target with the middle element and discard half the array each time. The number of steps k needed satisfies 2^k ≥ n, so k = ⌈log_2 n⌉. Therefore binary search runs in Θ(log n) time. Be careful about off-by-one details in implementation, but asymptotic cost remains logarithmic.

Other logarithmic examples
Balanced binary search trees (AVL, red-black) keep height proportional to log n, so operations like search, insert and delete take Θ(log n) time. Heap operations like insertion or extract-min also traverse tree height Θ(log n). Loops that double or halve an index (i*=2 or i/=2) run in Θ(log n) iterations.

Why logarithmic factors matter
Logarithmic-time algorithms scale very well. For example, for n = 1,000,000, log_2 n ≈ 20, which means dramatic speed compared to linear scans. When designing systems for large data sets, replacing linear-time operations with logarithmic ones can produce large practical improvements, even if constants differ.

Combinations with other terms
Often complexity includes logarithmic factors like n log n for sorting or n + log n for combined operations. Interpret such combinations: n log n means doing Θ(log n) work for each of n items (as in mergesort where there are log n levels each costing Θ(n)). In practice, check whether constants or smaller terms matter for the actual input sizes encountered.

📌 Examples
  • Binary search on 16 items needs ≤ 4 comparisons because log_2 16 = 4.
  • Loop doubling index: for(i=1;i<=n;i*=2) runs about log_2 n times.
  • Balanced BST operations proportional to tree height Θ(log n).
  • Heapify or bubble-up operations traverse O(log n) levels in a heap of size n.
🧮 Formulas
  1. Halving steps k satisfy 2^k ≥ n ⇒ k = ⌈log_2 n⌉
  2. Change of base: log_a n = log_b n / log_b a ⇒ Θ(log n) independent of base
📊 Visual ideas
Plot of log n versus n to show slow growth over large range.
Illustration of binary search splitting array into halves at each step.
💻7

Recurrences and solving divide-and-conquer relations

Where recurrences come from
Recurrences model running time of recursive algorithms by expressing T(n) in terms of T on smaller inputs. A typical divide-and-conquer recurrence is T(n) = a·T(n/b) + f(n) where the problem is split into a subproblems each of size n/b and f(n) is the cost to divide and combine. Understanding how to solve such recurrences is essential to analysing recursive algorithms.

Methods to solve recurrences
Three common methods are substitution, recursion tree and Master Theorem. Substitution involves guessing a solution form and proving it by induction. The recursion-tree method visualises costs at each level: draw the tree of recursive calls, label node costs, sum costs across levels and determine which level dominates. The Master Theorem provides direct answers for many standard recurrences of the form aT(n/b)+f(n).

Recursion-tree detailed use
Build the tree: root has cost f(n), level 1 has a nodes each cost f(n/b), level 2 has a^2 nodes with cost f(n/b^2), and so on down to leaves of size ~1. Number of levels is about log_b n. Sum cost per level and then sum across levels. Compare total cost of internal nodes and total cost at leaves to see which part dominates asymptotically.

Examples with explanation
Mergesort: a=2, b=2, f(n)=Θ(n). Recursion tree shows each of log n levels contributes Θ(n) so total Θ(n log n). Binary search: T(n)=T(n/2)+Θ(1) has constant per level and log n levels → Θ(log n). For T(n)=T(n-1)+Θ(1) the tree is a path of length n with constant cost per level → Θ(n).

Irregular recurrences and practical tips
Some recurrences are not balanced or have non-polynomial f(n); Master Theorem may not apply. In such cases use substitution or adapt the recursion-tree carefully. When writing exam answers, state base cases, assumptions (such as n power of b if needed), and show either a clear induction proof or a level-wise sum from the recursion tree to reach the final Θ bound.

📌 Examples
  • Mergesort recurrence T(n)=2T(n/2)+n → Θ(n log n) via recursion-tree or Master Theorem.
  • Binary search T(n)=T(n/2)+1 → Θ(log n).
  • Linear recursion T(n)=T(n-1)+1 → Θ(n).
  • T(n)=3T(n/2)+n leads to n^{log_2 3} dominating resulting in Θ(n^{log_2 3}).
🧮 Formulas
  1. General recurrence for divide-and-conquer: T(n)=a·T(n/b)+f(n).
  2. Number of levels ≈ log_b n when dividing by b each time.
📊 Visual ideas
Recursion tree for mergesort showing cost n at each level and log n levels.
Recursion tree for T(n)=T(n-1)+1 showing linear depth and unit cost per level.
💻8

Master Theorem: statement and application

Why the Master Theorem is useful
The Master Theorem provides a quick mechanical way to solve a wide class of divide-and-conquer recurrences of the form T(n) = a·T(n/b) + f(n) with constants a ≥ 1 and b > 1. It compares the non-recursive cost f(n) with the function n^{log_b a} representing work at the leaves and gives one of three cases to determine Θ(T(n)). Using it saves time and gives clear answers for common algorithms.

The three cases in practical terms
Let k = log_b a. Case 1: If f(n) grows polynomially slower than n^k, i.e., f(n) = O(n^{k-ε}) for some ε > 0, then the leaf cost dominates and T(n) = Θ(n^k). Case 2: If f(n) = Θ(n^k), costs at all levels are balanced and T(n) = Θ(n^k log n). Case 3: If f(n) grows polynomially faster than n^k, i.e., f(n) = Ω(n^{k+ε}) and satisfies a regularity condition a·f(n/b) ≤ c·f(n) for some c < 1, then root cost dominates and T(n) = Θ(f(n)).

Applying the theorem step by step
To apply: identify a and b, compute k = log_b a and compare f(n) with n^k. Use exponent comparison or limit tests. If f(n) matches Case 3 you must also check the regularity condition; state it and verify it holds. If Master Theorem does not fit (unequal subproblem sizes, non-polynomial f(n), etc.) switch to recursion-tree or substitution.

Worked examples and explanation
Mergesort: a=2, b=2 so k=1 and f(n)=n ⇒ Case 2 ⇒ T(n)=Θ(n log n). For T(n)=3T(n/4)+n, compute k=log_4 3 ≈0.79; since f(n)=n grows faster than n^{0.79} we use Case 3 and get T(n)=Θ(n) provided regularity holds (it does). For T(n)=4T(n/2)+n, k=2 and f(n)=n is smaller (Case 1), so T(n)=Θ(n^2).

Limitations and exam advice
Master Theorem does not apply when subproblems are of unequal size or when f(n) involves extra logarithmic factors that change case boundaries. In exam answers, always show identification of a and b, compute k explicitly, state which case applies and show any required regularity verification for Case 3. If the theorem does not apply, state the reason and use substitution or recursion-tree instead.

📌 Examples
  • T(n)=2T(n/2)+n ⇒ k=1, f(n)=n ⇒ Case 2 ⇒ Θ(n log n).
  • T(n)=3T(n/4)+n ⇒ k≈0.79, f(n)=n ⇒ Case 3 ⇒ Θ(n).
  • T(n)=4T(n/2)+n ⇒ k=2, f(n)=n ⇒ Case 1 ⇒ Θ(n^2).
🧮 Formulas
  1. \[Master Theorem: compare f(n) with n^{log_b a} to select one of three cases.\]
  2. \[Case summaries: (1) f(n)=O(n^{k-ε}) ⇒ Θ(n^{k})\]
    \[(2) f(n)=Θ(n^{k}) ⇒ Θ(n^{k} log n)\]
    \[(3) f(n)=Ω(n^{k+ε}) and regularity ⇒ Θ(f(n)).\]
📊 Visual ideas
Recursion tree sketches that show leaf-dominated, level-balanced, and root-dominated cost shapes corresponding to the three cases.
Plot comparing f(n) and n^{log_b a} to visualise which term dominates.
💻9

Amortised analysis and its methods

What amortised analysis means
Amortised analysis finds the average cost per operation over a worst-case sequence of operations, giving a guaranteed bound on amortised cost. It differs from average-case analysis: amortised deals with deterministic worst-case sequences averaged across many operations, while average-case assumes a distribution over inputs. Amortised analysis is useful for data structures that have occasional expensive operations balanced by many cheap ones.

Three methods explained
The aggregate method computes the total cost of a sequence of n operations and divides by n to get the amortised cost per operation. The accounting method assigns an amortised charge to each operation, storing excess as credits to pay for future expensive operations; choose charges so the credit never goes negative. The potential method uses a potential function mapping states to stored energy; the amortised cost equals actual cost plus change in potential and this yields tight bounds for many structures.

Dynamic array in detail
Consider an array that doubles capacity when full. Most append operations cost O(1) to place an element. On resize, copying k elements costs O(k). Resizes happen at sizes 1,2,4,8,...; the total cost of all copies during n appends is less than 2n. Using aggregate method total cost ≤ cn for some constant c, so amortised cost per append is O(1). Accounting method explains this by charging each append 3 units: 1 pays for insertion and 2 saved to pay future copies; show credits always suffice.

Binary counter example
A binary counter increments by flipping bits; sometimes many bits flip (e.g., from 0111 to 1000), but these expensive increments are rare. Over 2^k increments total bit flips are bounded by about 2^{k+1}-1, so amortised flips per increment is constant: O(1). Use accounting or potential methods to formalise this by charging 2 credits per increment to pay for future flips.

Why amortised is important
Amortised analysis shows that data structures with occasional heavy operations still provide efficient guarantees over time. Examples include dynamic arrays, splay trees, queues implemented with two stacks, and hash tables that resize. In exams, clearly state the amortised analysis method used and show arithmetic or bookkeeping to justify the claimed bound.

📌 Examples
  • Dynamic array doubling: total cost for n appends ≤ 3n ⇒ amortised O(1) per append.
  • Binary counter: total bit flips ≤ 2n over n increments ⇒ amortised O(1) per increment.
  • Hash table with resizing by doubling gives amortised O(1) inserts.
🧮 Formulas
  1. Aggregate method: amortised cost = total_cost_of_n_ops / n.
  2. Dynamic array total copy cost ≤ 2n leading to amortised constant.
📊 Visual ideas
Timeline showing occasional large resize costs but low average cost per operation.
Bar chart of amortised cost vs actual cost with spikes for expensive operations.
💻10

Average-case, best-case and worst-case analyses

Definitions and differences
Best-case analysis examines the least amount of work an algorithm can do on any input of size n; worst-case examines the most; average-case gives the expected work assuming a probability distribution over inputs. Worst-case is used when guarantees are required; average-case is useful when inputs follow a known or reasonable distribution. Best-case is rarely used alone to judge an algorithm's usefulness.

How to compute average-case
Average-case complexity is the expected cost: sum_{inputs} cost(input) × P(input). For simple examples assume uniform distribution over positions or permutations, but always state that assumption. Average-case may require combinatorial counting; for linear search with uniform target position average comparisons = (n+1)/2, which is Θ(n).

Examples showing differences
Linear search: best-case Ω(1) if target is first, worst-case O(n) if target absent, average-case Θ(n) under uniform distribution. Quicksort: average-case Θ(n log n) if pivots are random; worst-case O(n^2) if partitioning is always unbalanced (e.g., choosing extreme pivots on sorted input). Hash tables: average lookup Θ(1) with good hashing; worst-case O(n) if collisions concentrate.

When each measure is used
Use worst-case when inputs can be adversarial or when a strict guarantee is needed. Use average-case when inputs are random or typical and the model is justified. Amortised analysis complements these by averaging costs over sequences rather than input distributions; choose the appropriate method depending on the question asked and state assumptions clearly.

Exam advice
When asked for average-case show the distribution assumption and compute the expected value. For worst-case clearly construct or describe the adversarial input that forces maximum cost. For best-case show the input that gives minimal cost but explain why best-case alone is insufficient for performance assurance.

📌 Examples
  • Linear search: best Ω(1), worst O(n), average Θ(n) assuming uniform position.
  • Quicksort: average Θ(n log n) with random pivot; worst-case O(n^2) for bad pivot choices.
  • Hash table: average Θ(1) lookup under uniform hashing; worst-case O(n) if collisions concentrate.
🧮 Formulas
  1. Average cost = Σ cost(input)·P(input) over all inputs.
  2. Linear search average comparisons = (n+1)/2 under uniform assumption.
📊 Visual ideas
Plot showing best, average and worst-case cost curves for a search algorithm.
Histogram of costs over input space showing mean and extremes.
🛳️11

Space complexity and memory trade-offs

Defining space complexity
Space complexity measures additional memory used by an algorithm as a function of input size n. Distinguish between total space (including input) and auxiliary space (extra memory the algorithm needs). For many problems auxiliary space is what matters when comparing in-place algorithms to those needing extra buffers.

Counting space usage
Count variables, arrays, recursion stack frames and auxiliary data structures. For recursion, maximum depth times space per call gives stack space. For dynamic allocations count total allocated entries. Express the result in O-notation: O(1) for constant extra space, O(n) for linear extra space, O(n^2) for quadratic space in matrices, and so on.

Common examples
Mergesort typically needs O(n) auxiliary space for merging, while heapsort is in-place with O(1) auxiliary memory. Recursive algorithms may use O(log n) or O(n) stack depending on depth. Adjacency matrix for a graph uses O(n^2) space, whereas adjacency lists use O(n + m) for m edges.

Time-space trade-offs
Some algorithms trade more memory to reduce time, such as memoization in dynamic programming which stores intermediate results to avoid recomputation. Other times low-memory environments require in-place algorithms even if slightly slower. Choose based on resource constraints of the target system.

Practical advice
When asked, clearly state whether you measure auxiliary space or total space. Give counts for arrays and recursion and simplify to asymptotic form. Consider memory locality and cache effects in practical implementations, though these are beyond asymptotic notation but important for performance engineering.

📌 Examples
  • In-place array reversal uses O(1) auxiliary space.
  • Mergesort uses O(n) auxiliary space for the temporary array.
  • Adjacency matrix for n vertices uses O(n^2) space; adjacency list uses O(n+m).
  • Memoized Fibonacci uses O(n) space to store results and reduces time from exponential to linear.
🧮 Formulas
  1. Auxiliary space S(n) often expressed as O(1), O(log n), O(n), O(n^2), etc.
  2. Recursion stack space = O(recursion depth).
📊 Visual ideas
Diagram comparing memory footprints of adjacency matrix and adjacency list for sparse and dense graphs.
Stack frame diagram illustrating recursion depth and per-call memory.
🌳12

Lower bounds, decision trees and impossibility results

Lower bounds explained
A lower bound shows that no algorithm in a given model can solve a problem faster than a certain asymptotic limit. Lower bounds are important: they tell us when further improvements are impossible under the model's assumptions and guide search for algorithms that meet the bound.

Decision-tree model for comparisons
For comparison-based sorting, model the algorithm as a decision tree where each internal node compares two elements and branches depending on the result. Each leaf represents a possible sorted order. To handle all n! input permutations, the tree must have at least n! leaves, so its height (worst-case number of comparisons) is at least log_2(n!). Using Stirling's approximation log_2(n!) = Θ(n log n), giving a lower bound Ω(n log n) for comparison sorts.

Implications and exceptions
This lower bound applies to any algorithm that only compares elements to determine order. Algorithms that use assumptions about keys, such as integer ranges, can beat this bound: counting sort or radix sort run in O(n + k) or O(n log k) under suitable conditions. Therefore lower bounds depend on the computational model and assumptions allowed.

Other lower bound techniques
Reduction is a technique: show that solving problem B fast would solve problem A which has a known lower bound; hence B also has the lower bound. Adversary arguments, information-theoretic arguments and communication complexity methods are other tools used in proving lower bounds.

Exam style and clarity
When asked for a lower bound, state the model (e.g., comparison-based), give the core argument (decision-tree size or reduction), and derive the asymptotic bound. For sorting, show why at least log_2(n!) comparisons are necessary and then apply Stirling to reach Θ(n log n). Be explicit about assumptions to avoid losing marks.

📌 Examples
  • Decision-tree for sorting 3 elements has 6 leaves (3!) and height at least log_2 6.
  • Comparison-based sorting lower bound: Ω(n log n) via log_2(n!).
  • Counting sort avoids comparison lower bound when keys are integers in small range k, time O(n + k).
🧮 Formulas
  1. Lower bound for comparison sorts: height ≥ log_2(n!) = Θ(n log n).
  2. Stirling: log(n!) ≈ n log n - n + O(log n).
📊 Visual ideas
Decision-tree sketch for sorting three items with 6 leaves.
Plot showing Ω(n log n) lower bound compared to algorithm curves.

Key Concepts

Asymptotic analysis
Studying how an algorithm's resource usage grows with input size, ignoring constant factors and lower-order terms.
Big O notation
An upper bound notation that describes the worst-case growth of a function up to constant factors.
Big Omega notation
A lower bound notation indicating at least how fast a function grows for large inputs.
Big Theta notation
A tight bound notation meaning a function grows both at most and at least as fast as another, up to constants.
Time complexity
A measure of the number of basic operations an algorithm performs as a function of input size.
Space complexity
A measure of the extra memory an algorithm requires as a function of input size.
Worst-case analysis
Complexity measured on the most expensive input of a given size.
Average-case analysis
Expected complexity assuming a probability distribution over inputs.
Best-case analysis
Complexity measured on the most favourable input of a given size.
Recurrence relation
An equation that defines a function in terms of its values on smaller inputs, commonly used for recursive algorithms.
Master Theorem
A tool to solve divide-and-conquer recurrences of the form T(n)=aT(n/b)+f(n) under certain conditions.
Amortised analysis
A method that averages the worst-case cost of operations over a sequence to give guaranteed per-operation cost.
Decision-tree model
A representation of comparison-based algorithms where internal nodes are comparisons and leaves represent outcomes, used to prove lower bounds.
Dominant term
The highest-order term in a function that determines its asymptotic growth for large inputs.
Logarithmic complexity
Growth proportional to log n, typical when problem size is reduced multiplicatively each step.
Polynomial time
Complexity that can be expressed as O(n^k) for some constant k, considered feasible for many problems.
Exponential time
Complexity growing like c^n for c>1, which becomes infeasible for moderate n.

Practice Questions

  1. Explain Big O notation and give one example. / बिग ओ नोटेशन की व्याख्या कीजिए और एक उदाहरण दीजिए।
    Show answer

    Big O notation gives an upper bound on how a function grows: f(n)=O(g(n)) means f(n) ≤ c·g(n) for some constants c and large n; for example 5n+10 = O(n). / बिग ओ नोटेशन किसी फलन के वृद्धि की ऊपरी सीमा देता है: f(n)=O(g(n)) का अर्थ है कि कुछ स्थिर c के लिये और पर्याप्त बड़े n पर f(n) ≤ c·g(n); उदाहरण के लिए 5n+10 = O(n)।

  2. What is the time complexity of binary search and why? / बाइनरी सर्च की समय जटिलता क्या है और क्यों?
    Show answer

    Binary search halves the search interval each step, so number of steps is about log_2 n; therefore time complexity is Θ(log n). / बाइनरी सर्च हर कदम पर खोज अंतराल को आधा कर देता है, इसलिए आवश्यक कदमों की संख्या लगभग log_2 n होती है; अतः समय जटिलता Θ(log n) है।

  3. Prove that 3n^2 + 7n + 5 = O(n^2). / प्रमाण कीजिए कि 3n^2 + 7n + 5 = O(n^2)।
    Show answer

    For n ≥ 1, 7n ≤ 7n^2 and 5 ≤ 5n^2, so 3n^2+7n+5 ≤ 3n^2+7n^2+5n^2 = 15n^2. Choose c=15 and n0=1; hence 3n^2+7n+5 ≤ c·n^2 for all n≥n0, proving O(n^2). / n ≥ 1 के लिये 7n ≤ 7n^2 और 5 ≤ 5n^2 होते हैं, अतः 3n^2+7n+5 ≤ 3n^2+7n^2+5n^2 = 15n^2. c=15 और n0=1 चुनें; इसलिए सभी n≥n0 के लिये 3n^2+7n+5 ≤ c·n^2 और O(n^2) सिद्ध होता है।

  4. Given T(n)=2T(n/2)+n, use Master Theorem to find T(n). / यदि T(n)=2T(n/2)+n है, तो मास्टर थ्योरम का उपयोग कर T(n) निकालिए।
    Show answer

    Here a=2, b=2 so n^{log_b a}=n. Since f(n)=n equals n^{log_b a}, it is case 2 of Master Theorem, giving T(n)=Θ(n log n). / यहाँ a=2, b=2 इसलिए n^{log_b a}=n है। क्योंकि f(n)=n उसी के बराबर है, यह मास्टर थ्योरम का केस 2 बनता है और T(n)=Θ(n log n) मिलता है।

  5. Explain amortised analysis with the dynamic array (doubling) example. / डायनेमिक एरे (डबलिंग) के उदाहरण के साथ अमोर्टाइज़्ड विश्लेषण समझाइए।
    Show answer

    When appending, most inserts are O(1); when array is full it resizes and copies elements, costing O(n). With doubling, resize events occur at sizes 1,2,4,8,... Total copy cost for n appends is less than 2n, so average cost per append is O(1). Thus amortised cost is constant. / ऐरे में जोड़ते समय अधिकतर इन्सर्ट O(1) होते हैं; जब ऐरे भर जाता है तो साइज बढ़ाकर सभी तत्व कॉपी करने का खर्च O(n) आता है। डबलिंग पर यह घटनाएँ शक्तियों पर होती हैं और n इन्सर्ट्स के लिए कुल कॉपी लागत 2n से कम रहती है, इसलिए प्रति इन्सर्ट औसत लागत O(1) होती है। अतः अमोर्टाइज़्ड लागत स्थिर है।

  6. What is the lower bound for comparison-based sorting and how is it derived? / तुलना-आधारित सॉर्टिंग के लिये निचला बंधन क्या है और इसे कैसे निकाला जाता है?
    Show answer

    Lower bound is Ω(n log n). Derived by decision-tree argument: a comparison sort must be able to produce any of n! permutations, so tree needs at least n! leaves. Height ≥ log_2(n!), and log_2(n!) = Θ(n log n) by Stirling, giving Ω(n log n). / निचला बंधन Ω(n log n) है। निर्णय-वृक्ष तर्क से निकाला जाता है: तुलना-आधारित सॉर्ट को n! संभावित अनुक्रमों में से किसी को उत्पन्न करना चाहिए, अतः निर्णय-वृक्ष में कम से कम n! पत्तियाँ होंगी। ऊँचाई ≥ log_2(n!) और Stirling से log_2(n!) = Θ(n log n) होता है, इसलिए Ω(n log n) मिलता है।

  7. Compute time complexity of nested loops: for(i=1;i<=n;i++) for(j=1;j<=i;j++) S++; / नेस्टेड लूप्स का समय जटिलता निकालिए: for(i=1;i<=n;i++) for(j=1;j<=i;j++) S++;
    Show answer

    Total increments = Σ_{i=1}^n i = n(n+1)/2 = Θ(n^2). Therefore time complexity is Θ(n^2). / कुल वृद्धि Σ_{i=1}^n i = n(n+1)/2 = Θ(n^2) है। अतः समय जटिलता Θ(n^2) है।

  8. Why is log base irrelevant in asymptotic notation? / असिंप्टोटिक नोटेशन में लोगारिथम के आधार का मतलब क्यों नहीं रखा जाता?
    Show answer

    Change of base formula: log_a n = log_b n / log_b a. Since 1/log_b a is a constant multiplier, base change only alters constants, which are ignored in asymptotic notation, so all logs are Θ(log n). / आधार परिवर्तन सूत्र: log_a n = log_b n / log_b a. क्योंकि 1/log_b a एक स्थिर गुणांक है, आधार बदलने से केवल स्थिर गुणांक बदलता है और असिंप्टोटिक नोटेशन में स्थिरांक अनदेखा किए जाते हैं, इसलिए सभी लोग Θ(log n) होते हैं।

  9. Find time complexity of naive Fibonacci recursion and explain improvement by memoization. / साधारण फ़ाइबोनैचि रिकर्सन की समय जटिलता निकालिए और मेमोइज़ेशन से सुधार समझाइए।
    Show answer

    Naive recursion T(n)=T(n-1)+T(n-2)+Θ(1) yields exponential time Θ(φ^n) where φ≈1.618. Memoization stores computed fib(k) values so each fib(k) computed once, giving O(n) time and O(n) space. / साधारण रिकर्सन T(n)=T(n-1)+T(n-2)+Θ(1) होने पर समय व्युत्पन्न रूप से बढ़ता है Θ(φ^n) जहाँ φ≈1.618। मेमोइज़ेशन गणना किए गए fib(k) को स्टोर कर देता है ताकि हर fib(k) केवल एक बार निकले, जिससे समय O(n) और स्थान O(n) हो जाता है।

  10. Give an example where average-case and worst-case complexities differ. / एक उदाहरण दीजिए जहाँ औसत-स्थिति और सबसे खराब-स्थिति की जटिलताएँ अलग हों।
    Show answer

    Quicksort: average-case Θ(n log n) if pivots split well on average; worst-case O(n^2) if pivots are always extreme (e.g., sorted input with unlucky pivot). Linear search average-case Θ(n) but best-case Ω(1). / क्विकसॉर्ट: औसत-स्थिति Θ(n log n) होती है यदि पिवट औसतन अच्छी तरह विभाजित करें; परन्तु सबसे खराब-स्थिति O(n^2) हो सकती है यदि पिवट हमेशा चरम हो (जैसे सॉर्टेड इनपुट और गलत पिवट)। लिनियर सर्च का औसत Θ(n) और सर्वोत्तम Ω(1) होता है।

  11. State and apply the limit test to compare f(n)=n and g(n)=n log n. / f(n)=n और g(n)=n log n की तुलना करने के लिये लिमिट टेस्ट बताइए और लागू कीजिए।
    Show answer

    Compute lim_{n→∞} f(n)/g(n) = lim n/(n log n) = lim 1/log n = 0. Since limit is 0, f(n) = o(g(n)) and n grows slower than n log n, so n = o(n log n) and n = O(n log n) but not Θ(n log n). / लिमिट निकालें: lim_{n→∞} n/(n log n) = lim 1/log n = 0। चूँकि सीमा 0 है, f(n)=o(g(n)) है और n की वृद्धि n log n से धीमी है, अतः n = o(n log n) तथा n = O(n log n) लेकिन n ≠ Θ(n log n)।

Related Laws & Principles

Explore all

Foundational laws & principles connected to this chapter — tap to open in the Laws Explorer.

Loading related laws…
Sourced from 0 content files · LLOS Learn · browse all chapters