10.3 · Representing Rooted Trees
Click a node — see how it appears in the parent-pointer array and in the left-child / right-sibling encoding.
parent array p[node]
| node | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|
left-child / right-sibling
| node | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|
click any node ↑
11.1 · Direct-Address Tables
Slot index = key. No hashing, no collisions — but the array must span the whole universe U.
idle
| Operation | Time | Space |
|---|---|---|
| insert / search / delete | O(1) | O(1) |
| whole table | — | O(|U|) |
Direct addressing is the ideal case: A[k] is a single address computation. It is only practical when the universe U is small. If |U| = 10⁶ but you store 5 keys, you allocated a million slots for five entries — that is the O(|U|) space cost that hashing fixes.
11.3 · Hash Functions
30 keys distributed into 11 buckets — compare the division, multiplication and universal methods.
idle
| Method | Formula | Notes |
|---|---|---|
| Division | k mod m | m should be prime & far from powers of 2 |
| Multiplication | ⌊m·(kA mod 1)⌋ | A ≈ (√5−1)/2, works for any m |
| Universal | ((ak+b) mod p) mod m | random a,b — expected chain length O(1) |
A hash function must be deterministic and uniform. With load factor α = n/m, an unsuccessful search scans on average α nodes in the chain — O(1+α). If the hash collapses everything into one bucket, α becomes n and you are back to O(n).
11.4 · Open Addressing
Every key lives in the table itself — collisions are resolved by probing. Watch the probe sequence.
idle
| Probing | Sequence | Risk |
|---|---|---|
| Linear | (h₁+i) mod m | primary clustering |
| Quadratic | (h₁+i²) mod m | secondary clustering |
| Double | (h₁+i·h₂) mod m | needs h₂(k) coprime with m |
Open addressing removes pointers, so the whole table is one contiguous array — excellent cache behaviour. The price is that the load factor α must stay below 1: as α → 1 the expected number of probes explodes to 1/(1−α). Deletion requires tombstones (a DELETED marker) rather than emptying the slot.
12.3 · BST Insertion & Deletion
Insert walks down the search path. Delete has three cases — leaf, one child, two children (transplant the successor).
idle
| Delete case | Action | Time |
|---|---|---|
| 0 children | splice out the leaf | O(1) |
| 1 child | replace node by its child | O(1) |
| 2 children | find successor, transplant | O(h) |
In the two-children case the successor is the minimum of the right subtree — the smallest key larger than the deleted one. Transplanting it preserves the in-order sequence. The cost is the height h: O(log n) when balanced, O(n) when the tree degenerates into a chain.
13.4 · Red-Black Deletion
Removing a black node creates a “double black” deficit that bubbles up via four fix-up cases.
Case 1 · sibling w is RED
Recolor w black, parent red, rotate parent. Converts to case 2/3/4.
Case 2 · w black, both children black
Paint w red, move the double-black up to the parent. O(log n) iterations.
Case 3 · w black, near child red
Recolor and rotate w to turn it into case 4.
Case 4 · w black, far child red
Recolor w with parent's colour, paint far child black, rotate parent. Terminates.
| Operation | Time | Space |
|---|---|---|
| delete | O(log n) | O(1) |
| single rotation | O(1) | O(1) |
| tree storage | — | O(n) + 1 bit |
A rotation is a pure pointer shuffle — constant time, no data movement. That is why red-black trees guarantee O(log n) inserts and deletes: at most O(log n) recolourings plus at most 3 rotations, each O(1).
14.1 · Rod Cutting (dynamic programming)
r[j] = max1≤i≤j ( p[i] + r[j−i] ) — each subproblem is solved once and reused.
| i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|
idle
| Approach | Time | Space |
|---|---|---|
| naïve recursion | O(2ⁿ) | O(n) stack |
| top-down + memo | O(n²) | O(n) |
| bottom-up DP | O(n²) | O(n) |
The naïve recurrence solves the same subproblem r[j] exponentially many times. DP stores each answer once: the inner loop over i costs j, summed over j gives Θ(n²). The s[j] array records the optimal first cut, letting us reconstruct the pieces in O(n).
14.2 · Matrix-Chain Multiplication
Choose the cheapest parenthesization of A₁…A₆. Dims: 30×35×15×5×10×20×25.
idle
| Approach | Time | Space |
|---|---|---|
| brute force parenthesizations | exponential | O(n) stack |
| DP over intervals | O(n³) | O(n²) |
m[i][j] = minimum cost to multiply Ai…Aj. There are O(n²) subproblems, each trying O(n) split points → Θ(n³). The s[i][j] table records the optimal split so the parenthesization can be rebuilt in O(n).
14.4 · Longest Common Subsequence
X = ABCBDAB · Y = BDCABA — fill the table, then trace back.
idle
| Approach | Time | Space |
|---|---|---|
| naïve recursion | O(2^(m+n)) | O(m+n) stack |
| DP table | O(mn) | O(mn) |
| DP + rolling rows | O(mn) | O(min(m,n)) |
If xi = yj the LCS grows by one; otherwise it is the max of dropping a character from either string. Filling the table costs Θ(mn); the traceback is O(m+n). Only two rows are needed for the length — full storage is needed to reconstruct the subsequence.
14.5 · Optimal Binary Search Trees
Keys with search probabilities p, dummy keys with q — minimise expected search cost.
idle
| Approach | Time | Space |
|---|---|---|
| exhaustive trees | Ω(4ⁿ/n^1.5) | O(n) stack |
| interval DP | O(n³) | O(n²) |
| Knuth optimisation | O(n²) | O(n²) |
e[i][j] is the expected cost of an optimal BST on keys i…j. Adding the weight w(i,j) accounts for every key on the path being one level deeper. The root[i][j] table records the chosen root, reconstructing the tree in O(n).
15.1 · Activity Selection (greedy)
Sort by finish time, then repeatedly take the next compatible activity.
idle
| Approach | Time | Space |
|---|---|---|
| DP over intervals | O(n²) | O(n) |
| greedy (earliest finish) | O(n log n) | O(1) |
The greedy choice — take the activity that finishes first — is safe because it leaves the largest possible remaining time for the rest. This is the exchange argument: any optimal solution can be swapped to include the earliest-finishing activity without getting worse. The O(n log n) comes entirely from the sort; the scan itself is linear.
15.3 · Huffman Codes
Repeatedly merge the two least-frequent nodes. Prefix-free code, minimum expected length.
idle
| Approach | Time | Space |
|---|---|---|
| naïve repeated min-scan | O(n²) | O(n) |
| binary heap / priority queue | O(n log n) | O(n) |
Huffman's algorithm is greedy but provably optimal: the two least-frequent symbols can always be placed as siblings at maximum depth. Merging them and recursing gives a prefix-free code — no codeword is a prefix of another — so decoding needs no separators. Each merge costs O(log n) with a heap → O(n log n).
15.4 · Offline Caching (Bélády's algorithm)
Evict the block whose next use is farthest in the future. Optimal, but requires knowing the future.
idle
| Algorithm | Misses | Needs future? |
|---|---|---|
| Bélády (optimal) | — | yes |
| LRU | — | no |
Bélády is the optimal offline paging algorithm. The greedy exchange argument proves that evicting the block used farthest in the future never hurts. Real systems cannot see the future, so they approximate it with LRU (least-recently used) — typically within a constant factor of optimal on real traces.
16 · Amortized Analysis · dynamic tables
A single insert can cost O(n), but the average over a sequence is O(1) with doubling.
idle
| Method | Argument | Bound / op |
|---|---|---|
| Aggregate | total cost ≤ 2n over n inserts | O(1) |
| Accounting | 3 credits per insert, 1 spent, 2 saved | O(1) |
| Potential | Φ = 2·num − size, amortized = 3 | O(1) |
With doubling, the total work to do n insertions is 1 + 1 + 2 + 1 + 4 + … = 2n − 1 = O(n), so the amortized cost per insert is O(1). The potential function Φ(T) = 2·num[T] − size[T] captures the “saved-up” work: every cheap insert raises Φ by 2, every expensive resize drops it — the amortized cost is exactly 3. Growing by a constant increment instead of doubling would give Θ(n) amortized.