Part IA · Lent
Algorithms 1
Sorting · Design Paradigms · Data Structures
Course Map
unreviewedThis course is about more than particular algorithms. The central idea is that algorithmic performance comes from structure: the structure of the problem, the recurrence, the data representation, and the invariant that makes correctness provable.
- Sorting and order statistics.
- Divide and conquer, dynamic programming, greedy design.
- Core data structures: trees, heaps, hash tables, queues, lists.
- Prove correctness using loop invariants or data-structure invariants.
- Solve or estimate recurrence relations.
- Choose a data structure that improves the asymptotic cost of an operation set.
- Reason about worst case, expected case, space use, and practical tradeoffs.
- Performance comes from structure — of the problem, the recurrence, and the data representation.
- Four things to be able to do: prove correctness by invariant, solve recurrences, pick a structure that improves an operation set, and argue tradeoffs.
- Every algorithm here has an invariant (why it is correct) and a recurrence or count (why it costs what it costs). Know both.
Correctness and Algorithm Analysis
unreviewedAn algorithm is correct if it terminates and returns the right output for every valid input instance. In this course correctness carries as much weight as speed, and most marks are lost by asserting a bound rather than justifying it.
Asymptotic Notation
- Upper bound — grows no faster than .
- Lower bound — grows at least as fast as .
- Tight bound — both of the above.
- Strictly slower growth.
- Strictly faster growth.
Cost Models
- For comparison sorts, count key comparisons.
- Ignore fixed-cost arithmetic and control overhead when it only moves constant factors.
- Remember the hidden costs: cache behaviour, pointer chasing, hash computation, equality checks.
Loop Invariants
Where an algorithm contains a loop, correctness is usually proved by naming a property every iteration preserves.
- Initialisation — true before the loop starts.
- Maintenance — one iteration preserves it.
- Termination — on exit, invariant plus stop condition imply correctness.
- Correct = terminates and right output on every valid input.
- upper, lower, tight; and are the strict versions.
- Comparison sorts are costed in key comparisons; constant factors and control overhead are ignored.
- Loop invariant proof has exactly three parts: initialisation, maintenance, termination. Termination is the invariant plus the negated loop guard.
A loop-invariant proof establishes correctness at termination by combining the invariant with:
Which statement is true of f(n) ∈ o(g(n)) but not of f(n) ∈ O(g(n))?
Why do comparison sorts get costed in key comparisons rather than in total operations?
Insertion sort and merge sort are both correct. Explain why an invariant argument is natural for one and awkward for the other.
Recurrence Relations
unreviewedRecurrences are how divide-and-conquer costs get expressed. Four methods appear in the course, and the exam expects you to pick the cheapest one that works.
Solving Methods
- Substitute and spot a pattern — fastest when the expansion is regular.
- Recursion tree — best when work per level is easy to total.
- Guess and verify — needs a candidate, then induction.
- Master theorem — only for the standard shape below.
Master Theorem
For a recurrence of the form
compare against , the total work done at the leaves of the recursion tree.
- If is polynomially smaller, the recursive work dominates: .
- If they are the same order, every level costs the same and you pick up a factor of : .
- If is polynomially larger and the regularity condition holds for some , the non-recursive work dominates: .
- — merge sort.
- — binary search.
- — tree traversal.
- Four methods: substitution, recursion tree, guess-and-verify, master theorem.
- Master theorem — for , compare with : smaller → recursion dominates; equal → extra ; larger (plus regularity) → dominates.
- Three to know cold: , , .
In T(n) = a·T(n/b) + f(n), the master theorem decides the case by comparing f(n) against:
T(n) = 2T(n/2) + Θ(n) solves to:
Which recurrence describes binary search?
Case 3 of the master theorem carries a regularity condition. What breaks without it, and can you describe an f(n) that fails it?
Quadratic Sorting Algorithms
unreviewedThese are algorithms and they still matter — insertion sort is what the good sorts drop into once subarrays get small enough that a tight loop beats the recursion overhead.
| Algorithm | Worst | Best | Stable | In-place |
|---|---|---|---|---|
| Insertion sort | Yes | Yes | ||
| Selection sort | No | Yes |
Insertion Sort
Incremental algorithm: maintain a sorted prefix and insert the next key into the right place by shifting larger elements right.
- Worst and average case: . Best case on already-sorted input.
- Stable and in-place.
- Very good for small arrays because the loop body is tight.
Canonical Invariant
At the start of iteration , the prefix is sorted and contains exactly the original elements from those positions.
Selection Sort
Repeatedly select the smallest remaining item and place it into the next position.
- Cost in every case — it scans the whole remaining array regardless of order.
- Usually not stable unless carefully adapted.
- Can reduce the number of writes compared with insertion sort.
They are not just “bad algorithms”. They teach invariant-based correctness, and insertion sort appears as a subroutine in stronger algorithms, including median-of-medians and practical hybrid sorts.
- Both worst case, both in-place. Insertion sort is stable, selection sort is not.
- Insertion sort best case on sorted input — why it is the small-array fallback inside faster sorts.
- Selection sort best case is still : it always scans the whole remainder. It does fewer writes.
- Insertion sort invariant: at the start of iteration , is sorted and holds exactly those original elements.
Selection sort's best-case running time is:
Why is insertion sort used as the base case inside faster sorting algorithms?
Both insertion sort and selection sort are Θ(n²) and in-place. Give a concrete situation where you would still prefer each one.
Merge Sort and Quicksort
unreviewedMerge Sort
Classic divide-and-conquer: split in half, recursively sort both halves, then merge.
- Stable: yes.
- Guaranteed worst case — no bad inputs.
- Memory behaviour matters: textbook merge sort uses extra space; the slides note bottom-up merge sort can be in-place.
Quicksort
Partition around a pivot, then recursively sort the two sides.
- Best case: balanced split every time, .
- Any constant-ratio split still gives — a 99:1 split is asymptotically fine.
- Worst case: constant-and-rest split every time, .
- Expected case with sensible or random pivots.
- In-place in the standard array partition variants; not stable in that form.
Partition Correctness
The slides prove PARTITION with the invariant that elements left of the boundary are pivot, elements between and are pivot, and the pivot itself is preserved at the end until the final swap.
Pivot Strategy Tradeoffs
- Naive last or first element — vulnerable to sorted or adversarial input.
- Random input shuffling — makes all orderings roughly equally likely.
- Random pivot — removes the dependence on input order.
- Median-of-three — lowers the probability of an extreme pivot.
- Three-way partition — important when many duplicate keys occur.
- Median-of-medians — deterministic good pivot, stronger worst-case guarantee, larger constants.
- Merge sort — guaranteed, stable, usually not in-place.
- Quicksort — expected , worst , in-place, not stable. Any constant-ratio split is still .
PARTITIONinvariant: left of is pivot, from to is pivot, pivot swapped into place at the end.- Pivot choices, weakest to strongest: fixed → shuffle → random → median-of-three → median-of-medians. Three-way partition for many duplicates.
Quicksort splits every subarray in a 99:1 ratio. Its running time is:
Which resource does merge sort trade to obtain its worst-case guarantee?
In the PARTITION invariant, elements strictly between index i+1 and j-1 are:
Randomising the pivot does not improve quicksort's worst case, which stays Θ(n²). Explain why it is nevertheless the standard fix.
Heapsort and Heaps
unreviewedA binary heap is a semi-structured array representation of a nearly-complete binary tree.
Heap Properties
- Structural — complete except possibly the bottom level, which fills left to right.
- Ordering — in a max-heap, a parent key is at least each child key; symmetrically for min-heaps.
Array Mapping
The tree needs no pointers because position encodes structure:
Main Operations
REHEAPIFY— .FULL-HEAPIFY— , not .EXTRACT-MAXorEXTRACT-MIN— .PEEK— .INSERT, key increase and decrease — .
Heapsort
- Build a max-heap from the array in .
- Repeatedly swap the root with the final item in the heap region.
- Shrink the heap by one and reheapify the root.
Total cost . It is in-place but not stable.
- Two properties: structural (complete, fills left to right) and ordering (parent children in a max-heap).
- Array mapping: , , — no pointers needed.
FULL-HEAPIFYis , not . Most nodes are near the bottom and cheap to fix. This is the single most examined trap in the topic.PEEK; reheapify, extract and insert all .- Heapsort: build max-heap , then repeatedly swap-root-and-shrink. , in-place, not stable.
Building a heap from an unsorted array of n elements costs:
In the array representation of a binary heap, the parent of node i is at index:
Heapsort is:
Explain why FULL-HEAPIFY is Θ(n) while heapsort as a whole is Θ(n log n), given that both are built from the same reheapify operation.
Sorting in Linear Time
unreviewedComparison sorting has an lower bound — a decision tree with leaves has height at least . To beat it you must stop comparing and start using structure in the keys themselves.
Counting Sort
For keys drawn from a bounded integer range .
- Time — linear only while .
- Stable, provided the output fill runs right to left as in CLRS and the slides.
- Not comparison-based.
- Usually not in-place, because of the output array and the count array .
Radix Sort
Sort by digits from least significant to most significant, using a stable sort at each pass.
where is the number of digits and is the digit range. Stability at each pass is what makes the earlier passes survive the later ones — with an unstable inner sort the algorithm is simply wrong.
Bucket Sort
For values uniformly distributed over a known interval such as .
- Average case — under the uniformity assumption.
- Worst case if the buckets become highly uneven.
Stability
A stable sort preserves the relative order of items with equal keys. It matters directly for radix sort, and indirectly wherever a sort is applied on top of an earlier ordering.
- Comparison sorting is — decision tree with leaves. Beating it requires assumptions about the keys.
- Counting sort , stable, not in-place. Linear only while .
- Radix sort , least significant digit first, requires a stable inner sort or it is wrong.
- Bucket sort average under uniformity, worst.
Radix sort requires the sort used at each digit pass to be stable because:
Counting sort runs in Θ(n + k). It stops being a linear-time sort when:
Counting sort beats the Ω(n log n) comparison lower bound. Explain precisely why that is not a contradiction.
Median and Order Statistics
unreviewedThe th order statistic is the th smallest element. The selection problem asks for it without fully sorting.
Quickselect
The same partition idea as quicksort, but recurse only into the side containing the target rank.
- Expected ; worst case with bad pivots .
- Much faster in practice than sorting when only one rank is needed.
Median-of-Medians
Deterministic pivot selection guaranteeing a useful ratio split.
- Group the elements in fives.
- Sort each group and take its median.
- Recursively select the median of those medians.
- Use that as the pivot.
This guarantees at least about elements on each side, so the recursion is
The two recursive terms sum to strictly less than , which is exactly why the total collapses to linear.
Quicksort with Median-of-Medians
If distinct keys allow the true median to be used at every step, quicksort gets a guaranteed balanced split and therefore worst-case time. The tradeoff is much larger constants — it is a proof device more than a practical sort.
- Quickselect — partition, recurse into one side only. Expected , worst .
- Median-of-medians — groups of five, medians of groups, recursively select their median, use as pivot. Guarantees roughly either side.
- ; linear because .
- Quicksort with MoM pivots is worst case, but the constants make it impractical.
Median-of-medians groups elements in fives. The resulting recurrence T(n) = T(⌈n/5⌉) + T(7n/10 + 6) + Θ(n) is linear because:
Quickselect's expected running time is:
Quicksort with median-of-medians pivots achieves Θ(n log n) worst case, yet nobody uses it. Why is it still worth knowing?
Algorithm Design Paradigms
unreviewed| Paradigm | Core Idea | When It Works Best |
|---|---|---|
| Incremental | Grow a partial solution one step at a time. | Local maintenance is cheap. |
| Divide and conquer | Split into independent subproblems. | Subproblems do not overlap much. |
| Dynamic programming | Reuse overlapping subproblem answers. | Optimal substructure and overlap. |
| Greedy | Commit to the best-looking local move. | Greedy-choice property and optimal substructure. |
- Incremental — grow a partial solution; needs cheap local maintenance.
- Divide and conquer — independent subproblems.
- Dynamic programming — overlapping subproblems plus optimal substructure.
- Greedy — greedy-choice property plus optimal substructure.
- D&C against DP is decided by overlap, not by whether it recurses.
Dynamic Programming
unreviewedDP is for optimisation problems with optimal substructure and overlapping subproblems. Both conditions are required: without substructure the recurrence is invalid, without overlap you have gained nothing over plain recursion.
Canonical Workflow
- Characterise the structure of an optimal solution.
- Write a recurrence for the value of an optimal solution.
- Compute the values by memoisation or bottom-up filling.
- If needed, reconstruct the actual solution from the table.
Top-Down against Bottom-Up
| Approach | Advantages | Disadvantages |
|---|---|---|
| Top-down memoisation | Only solves the subproblems actually needed; the recurrence translates directly. | Uses the recursion stack. |
| Bottom-up tabulation | No recursion stack; often a simpler loop once the fill order is known. | May solve subproblems that are never used. |
Example Style
The slides use a rod-cutting and VM-hosting optimisation example. The naive recursive version is exponential because it recomputes the same suffix problems repeatedly; memoisation collapses it to polynomial time by making each distinct subproblem cost once.
- Needs both optimal substructure and overlapping subproblems.
- Workflow: characterise the optimal structure → write the value recurrence → memoise or tabulate → reconstruct if asked.
- Top-down solves only what is needed but uses the stack; bottom-up avoids the stack but may compute unused entries.
- Signal: the same parameter values recurring in the recursion tree.
Dynamic programming is the right paradigm when a problem has optimal substructure and:
Which is a genuine advantage of top-down memoisation over bottom-up tabulation?
A colleague memoises a divide-and-conquer algorithm whose subproblems do not overlap. What happens, and what does that tell you about what DP actually buys?
Greedy Algorithms
unreviewedA greedy algorithm picks one locally optimal move and commits to it immediately. That is only valid when local optimality is globally safe, which has to be proved, not assumed.
What Must Be True
- There is a meaningful greedy first move.
- There always exists an optimal solution that starts with that greedy move — the greedy-choice property.
- After making that move, the remaining subproblem is itself optimally solvable — optimal substructure.
Good and Bad Examples
- Fractional bag-filling by value density — greedy works, because a fraction of the best item is always available.
- Rod cutting and VM hosting — greedy fails, because the locally best first cut can destroy global optimality.
- Activity selection — greedy works, because the earliest-finishing choice leaves the most room for later ones.
Activity Selection
After sorting activities by finishing time, repeatedly choose the earliest-finishing compatible activity. The goal is maximum cardinality, not maximum occupied time — a common misreading that produces a different and wrong algorithm.
- Requires the greedy-choice property (some optimal solution starts with the greedy move) and optimal substructure.
- Works: fractional bag-filling by density, activity selection by earliest finish time.
- Fails: rod cutting — the best first cut can destroy global optimality.
- Activity selection maximises the number of activities, not the time occupied.
- Prove it by exchange: swap an optimal solution's first move for the greedy one and show it is no worse.
The greedy-choice property states that:
Activity selection chooses the earliest-finishing compatible activity in order to maximise:
Rod cutting has optimal substructure but greedy fails on it, while fractional bag-filling succeeds. What structural difference decides this?
Elementary Data Structures
unreviewedPointers
Pointers are runtime references to objects by base address. They are what make dynamic structures such as linked lists and trees possible. NIL denotes the absence of a target.
Stacks
LIFO structure supporting PUSH and POP. Arrays give bounded-capacity stacks easily; linked lists give unbounded ones cleanly.
Queues
FIFO structure, typically implemented with circular arrays or linked lists.
Linked Lists
- Singly linked — cheaper, but deletion and backward context are awkward.
- Doubly linked — easier deletion and bidirectional traversal, at the cost of an extra pointer per node.
- Cyclic variants simplify end cases by removing
NILfrom the ends.
Rooted Trees
Nodes may carry parent pointers, fixed or variable arity, and child lists. The choice of representation changes the complexity of the operations, which is the general lesson: representation is a performance decision, not a formatting one.
- Stack LIFO, queue FIFO; both do their core operations in .
- Singly linked is cheaper; doubly linked buys easy deletion and reverse traversal for one extra pointer; cyclic forms remove
NILend cases. - Representation choice — parent pointers, arity, child lists — is what sets the operation costs.
Binary Search Trees
unreviewedA BST stores key and payload pairs so that every key in the left subtree is strictly smaller and every key in the right subtree is strictly larger.
Supported Operations
SEARCH,INSERT,DELETE.MINIMUM,MAXIMUM.PREDECESSOR,SUCCESSOR.
Performance
- Expected cost on a random-shaped tree .
- Worst case , when the tree degenerates into a chain.
Key Structural Points
- No duplicate keys, if you want the classical clean guarantees.
PREDECESSORandSUCCESSORneed parent pointers in the textbook implementation.- Deletion is the subtle operation: the zero-child, one-child and two-child cases all differ, and the two-child case replaces the node with its successor.
- BST property: left subtree strictly smaller, right subtree strictly larger.
- expected on random shape, worst case when it degenerates to a chain.
- Deletion has three cases — zero, one and two children; the two-child case promotes the successor.
PREDECESSORandSUCCESSORneed parent pointers in the textbook form.- Sorted insertion order is the degenerate case, which is the whole motivation for balancing.
Inserting already-sorted data into an unbalanced BST produces:
Deleting a node with two children is handled by:
A BST has O(log n) expected cost. Explain why that is a weaker claim than a red-black tree's O(log n) worst case, and when the difference matters in practice.
Balanced Trees: B-Trees and Red-Black Trees
unreviewedB-Trees
Designed for disk-based settings, where following a pointer can cost millions of CPU cycles. The goal is to minimise tree height, because height is the number of expensive accesses.
B-Tree of Minimum Degree T
- Non-root internal nodes store between and keys.
- A node with keys has children.
- All leaves sit at the same depth.
- Separator keys partition the child key ranges.
Height is , hence search, insert and delete.
B-Tree Insert and Delete
- Insert by splitting full nodes, ideally pre-emptively on the way down, so no second pass is needed.
- Delete by redistributing from siblings or merging with them, preserving minimum and maximum occupancy.
- The root is special: splitting it increases the height, and losing its last key reduces it. This is why B-trees stay perfectly balanced.
Red-Black Trees
RB trees are BSTs with colour constraints that enforce approximate balance.
Properties
- Each node is red or black.
- The root is black.
- Leaves are black sentinel nodes.
- A red node has black children — no two reds in a row.
- Every path from a node down to its descendant leaves contains the same number of black nodes.
Together these bound the height at , giving worst-case operations. Properties 4 and 5 are the load-bearing pair: equal black-height fixes a floor, and no-two-reds stops any path exceeding twice it.
2-3-4 Tree Isomorphism
The slides connect RB trees explicitly to B-trees of minimum degree 2 — 2-3-4 trees. This is conceptually important: a red node encodes a key fused into its black parent's node, so an RB tree is a 2-3-4 tree drawn with binary links.
Rotations
Local restructuring that preserves the global key order, used inside the balancing logic for RB insert and delete.
- B-trees minimise height because height counts disk accesses. Non-root nodes hold to keys, a node with keys has children, all leaves at equal depth. Height .
- Insert by splitting full nodes on the way down; delete by redistributing or merging. Only root split and root collapse change the height.
- Red-black — root black, leaves black sentinels, no red node has a red child, equal black-height on every downward path. Height , so worst case.
- An RB tree is a 2-3-4 tree (B-tree of degree 2) in binary form: red nodes are keys fused into their black parent.
- Rotations restructure locally while preserving key order.
B-trees are designed to minimise tree height specifically because:
Which pair of red-black properties together bound the height at 2·log₂(n+1)?
A red-black tree is isomorphic to which structure?
B-trees and red-black trees both give O(log n) worst-case operations. Explain why the course presents them as answers to different questions.
Priority Queues
unreviewedA priority queue maintains a dynamic set and supports repeated access to the extreme-priority item.
Operations
MINIMUMandMAXIMUMEXTRACT-MINandEXTRACT-MAXINSERTDECREASE-KEYandINCREASE-KEY
| Implementation | Peek at extremum | Other core operations |
|---|---|---|
| Binary heap | ||
| Red-black tree |
The heap wins on peeking at the extremum and on constant factors. An RB tree may be preferable when richer dictionary-like operations — search by arbitrary key, ordered traversal, predecessor and successor — are needed alongside the queue behaviour.
- Operations: peek, extract, insert, and increase or decrease key.
- Heap — peek , everything else . Better constants.
- RB tree — all , but also gives search, ordered traversal, predecessor and successor.
- Choose on what else you need, not on the queue operations alone.
Hash Tables
unreviewedHash tables implement the dictionary operations INSERT, SEARCH and DELETE in expected near-constant time, provided the hash function and load-factor management behave.
Hashing Issues
- Range reduction — hash values usually need reducing modulo the table size.
- Performance — real hash functions can be expensive to compute.
- Collisions — unavoidable, by the pigeonhole principle.
- Entropy and selectivity — similar or low-entropy keys can cluster badly.
Chaining
Table entries point to linked lists. With load factor for slots, operations take expected time under simple uniform hashing.
Variants from the Slides
- Append, or replace an existing binding.
- Keep the lists sorted by key.
- Push on the head with tombstone-style logical deletion.
- Push on the head and delete from the list, giving stack-like behaviour per key.
Open Addressing
Store items directly in table slots and resolve collisions with a probe sequence.
Probe Schemes
- Linear probing
- Simple and cache-friendly, but suffers primary clustering — occupied runs grow and merge.
- Quadratic probing
- Avoids primary clustering; keys with the same initial slot still follow the same path, which is secondary clustering.
- Double hashing
- The best of the three: the step size itself depends on the key, so colliding keys diverge immediately.
Deletion in Open Addressing
You cannot simply clear a slot, because a later search would stop at the gap and miss items further along the probe sequence. A marker or tombstone is required.
Resizing
Manage both live keys and tombstones. Rehash into a larger, same-sized or smaller table depending on occupancy and marker count — a table full of tombstones needs rehashing even though it holds few keys.
- Dictionary operations in expected — conditional on the hash function and the load factor, not free.
- Chaining — expected where .
- Open addressing — linear probing (primary clustering), quadratic probing (secondary clustering), double hashing (best; step depends on the key).
- Deletion needs tombstones — clearing a slot breaks later probe sequences.
- Resizing must account for tombstones as well as live keys.
In open addressing, a deleted entry must be marked with a tombstone rather than cleared because:
Double hashing improves on quadratic probing because:
With chaining and load factor α = n/m, the expected cost of a search is:
A hash table with a table twice the size of its live key count is performing badly. Give two distinct explanations consistent with that.
Core Comparison Table
unreviewed| Method | Worst case | Average or expected | Stable | In-place |
|---|---|---|---|---|
| Insertion sort | Yes | Yes | ||
| Selection sort | No | Yes | ||
| Merge sort | Yes | Usually no | ||
| Heapsort | No | Yes | ||
| Quicksort | No | Yes | ||
| Quicksort, MoM pivots | No | Yes | ||
| Counting sort | Yes | No | ||
| Radix sort | If inner sort is | No | ||
| Bucket sort | Depends on handling | No |
- Guaranteed : merge sort, heapsort, quicksort with MoM pivots.
- Stable: insertion, merge, counting; radix if its inner sort is.
- In-place: insertion, selection, heapsort, quicksort.
- Both stable and guaranteed: merge sort — at the cost of memory.
- Both in-place and guaranteed: heapsort — at the cost of stability.
Exam Use
unreviewedWhat Strong Answers Usually Include
- The right asymptotic bound, with best, worst and expected case clearly distinguished.
- The invariant or structural property that makes the algorithm correct.
- The recurrence, and the method used to solve it.
- The data-structure property that enables the runtime — not just a list of operations.
- The tradeoff: memory, stability, in-place behaviour, worst-case robustness, or constant factors.
Common Mistakes
- Claiming
BUILD-HEAPis rather than . - Forgetting that any constant-ratio quicksort split is still .
- Using DP where subproblems do not overlap, or greedy without proving the greedy-choice property.
- Treating BST average-case behaviour as though it were a worst-case guarantee.
- Forgetting tombstones in open-addressing deletion.
- Calling a sort stable without checking its behaviour on equal keys.
- Answer with: the bound (case-distinguished), the invariant, the recurrence and its method, the enabling structural property, and the tradeoff.
- Traps:
BUILD-HEAPis ; constant-ratio quicksort splits are still ; BST average case is not a guarantee; open addressing needs tombstones; check stability on equal keys. - State why the structure forces the runtime, not just what the runtime is.