Part IV · V  —  Advanced Design & Analysis

Data Structures & Algorithms, Animated

Every operation below is animated and carries its time and space bound. Watch the step counters to see what the big-O really costs.

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 O(1) lookup LCRS: 2 pointers / node space O(n)

parent array p[node]

node1234567

left-child / right-sibling

node1234567

click any node ↑

11.1 · Direct-Address Tables

Slot index = key. No hashing, no collisions — but the array must span the whole universe U.

insert O(1) search O(1) delete O(1) space O(|U|)

idle

OperationTimeSpace
insert / search / deleteO(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.

expected O(1+α) α = n/m worst O(n)

idle

MethodFormulaNotes
Divisionk mod mm 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 mrandom 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.

avg O(1/(1−α)) no pointers worst O(n)

idle

ProbingSequenceRisk
Linear(h₁+i) mod mprimary clustering
Quadratic(h₁+i²) mod msecondary clustering
Double(h₁+i·h₂) mod mneeds 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).

insert O(h) delete O(h) worst h = n

idle

Delete caseActionTime
0 childrensplice out the leafO(1)
1 childreplace node by its childO(1)
2 childrenfind successor, transplantO(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.

delete O(log n) each fix-up O(1) space O(1)

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.

OperationTimeSpace
deleteO(log n)O(1)
single rotationO(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.

DP O(n²) naïve O(2ⁿ) space O(n)
i 0123 45678

idle

ApproachTimeSpace
naïve recursionO(2ⁿ)O(n) stack
top-down + memoO(n²)O(n)
bottom-up DPO(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.

DP O(n³) brute force Ω(4ⁿ/n^1.5) space O(n²)

idle

ApproachTimeSpace
brute force parenthesizationsexponentialO(n) stack
DP over intervalsO(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.

DP O(mn) brute force O(2^(m+n)) space O(mn)

idle

ApproachTimeSpace
naïve recursionO(2^(m+n))O(m+n) stack
DP tableO(mn)O(mn)
DP + rolling rowsO(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.

DP O(n³) Knuth O(n²) space O(n²)

idle

ApproachTimeSpace
exhaustive treesΩ(4ⁿ/n^1.5)O(n) stack
interval DPO(n³)O(n²)
Knuth optimisationO(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.

greedy O(n log n) DP O(n²) space O(1)

idle

ApproachTimeSpace
DP over intervalsO(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.

greedy O(n log n) optimal prefix code space O(n)

idle

ApproachTimeSpace
naïve repeated min-scanO(n²)O(n)
binary heap / priority queueO(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.

optimal offline O(n log k) with heap space O(k)
cache size k = 3

idle

AlgorithmMissesNeeds 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.

aggregate 2n accounting 3 per op potential Φ = 2·num − size

idle

MethodArgumentBound / op
Aggregatetotal cost ≤ 2n over n insertsO(1)
Accounting3 credits per insert, 1 spent, 2 savedO(1)
PotentialΦ = 2·num − size, amortized = 3O(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.