Graphs: Maps, Mazes, and Exploring Without Getting Lost
Last week closed on a warning: everything the notebook touched shrank cleanly — peel a letter, cut a branch, and it never came back. Tonight we walk into the country where that stops being true. The subway map, your social network, your degree's prerequisite chart, the internet itself — they are all graphs: dots connected by links, where the links can loop back around. You can walk in circles. So before graphs give us anything, we have to learn the explorer's first skill: how to visit everything, once, without getting lost.
The ripple vs. the maze-walker: one map, thirteen dots, two completely different ways to explore it — tap any dot and watch the queue and the stack do their work, or ask for the shortest route between any two dots. And the wardrobe game: get a student dressed without ever putting shoes on before socks — then add one cursed rule and watch the whole morning become impossible. Every number is computed live.
Session planHow we will spend the two sessions
Tuesday, November 10 — Session 20
Thursday, November 12 — Session 21
Section 1The world stops shrinking politely
You already live on graphs. The subway map: stations connected by track. Your social network: people connected by friendships. Your degree: courses connected by "take this first" arrows. The internet: pages connected by hyperlinks, routers connected by cables. In every one of them, the object is the same two-ingredient thing:
A graph is a set of dots with links between some of them. The official names — a dot is a vertex, a link is an edge — are worth hearing once, because every book and every library uses them. We will mostly say dots and links, because that is what they are.
So what's new? Trees from last week were graphs too — dots and links, with one promise attached: no loops. Cut a branch and it fell away forever; every step down the org chart made the problem smaller. Graphs make no such promise. Subway lines form circles on purpose. Your friend's friend's friend can be you. And that one missing promise changes everything about how you explore:
Here is the good news hiding in that picture: the discipline turns out to be small. Two exploration strategies — one patient, one bold — plus a pocketful of breadcrumbs, and every graph on Earth opens up. Everything else this week (ordering tasks, finding islands) falls out of those two strategies almost for free.
Section 2How a computer holds a map
Before exploring a graph, a program has to hold one, and there are exactly two classic ways. Both answer the same question — "who is linked to whom?" — with opposite trade-offs. Take six people, A through F, with seven friendships:
In Python, the friends list is a dictionary of lists, and that's the entire data structure this week runs on:
friends = {
"A": ["B", "C"],
"B": ["A", "D"],
"C": ["A", "D", "E"],
"D": ["B", "C", "F"],
"E": ["C", "F"],
"F": ["D", "E"],
}
Which costume should you reach for? One honest sentence each. The friends list is lean: it stores only the links that exist, and real-world graphs are overwhelmingly sparse — two million users with 200 friends each is 400 million list entries, while the checkbox table for the same network is four trillion boxes, almost all of them recording the non-news that two strangers aren't friends. The table's one superpower is the instant lookup: "are A and D linked?" is one peek at one box, where the friends list has to skim A's whole list. Sparse graph, walking along links (which is everything we do tonight): friends list. Small dense graph, hammering "are these two linked?" questions: table. That's the whole buying guide.
That the instant lookup costs 2,000,000 × 2,000,000 = 4 trillion checkboxes — roughly 4 terabytes at a byte apiece — to store what the friends list holds in about 400 million entries, ten thousand times less. The table pays for every link that could exist; the list pays only for links that do. At 200 friends per user this network is about 0.01% full, so the table is 99.99% stored silence. (If the graph were small and dense — say, 100 dots nearly all pairwise linked — the teammate would be right. Always ask how full the table would be.)
Section 3The ripple: breadth-first search
Now, exploring. Strategy one is the patient one. Drop a stone in a pond where you're standing. The ripple reaches everything 1 link away first. Then everything 2 links away. Then 3. It never skips ahead — water doesn't tunnel. Computer scientists call this breadth-first search, BFS; we'll call it the ripple.
To run a ripple on a computer you need two pieces of bookkeeping, and both are household objects:
- The line at the door. When you visit a dot, its not-yet-seen neighbors get in line. You always serve the front of the line — first come, first served — so everyone from ring 1 gets served before anyone from ring 2, automatically. (The data structure is a queue, and you met it in Week 6: this is what queues are for.)
- Breadcrumbs. The moment a dot gets in line, drop a breadcrumb on it: "seen." A dot with a breadcrumb never gets in line again. This is the discipline from §1 — it is exactly what stops the loop in the gold pentagon from trapping you. No breadcrumbs, infinite circles; breadcrumbs, every dot visited exactly once. (It's the humblest notebook we've ever kept: it doesn't even store answers, just "been here.")
from collections import deque def ripple(friends, start): """Visit everything reachable from start, nearest rings first.""" line = deque([start]) # the line at the door crumbs = {start} # breadcrumbs: already seen while line: person = line.popleft() # serve the front — first come, first served print(person) for buddy in friends[person]: if buddy not in crumbs: # no breadcrumb yet? crumbs.add(buddy) line.append(buddy) # back of the line
Ten lines, and two of them are the whole story: popleft (patience — serve the nearest first) and crumbs (discipline — never twice). Want the actual shortest route, not just its length? When a buddy gets in line, jot down who put them there; walking those jottings backward from the destination spells out the route. You've seen that move twice already — it's the walk-back from the DP weeks, wearing hiking boots.
crumbs set from the code above and run it on the §2 friendship graph. What actually happens, and on which dots?It never finishes. A gets in line, A visits B, B puts A back in line (B's friends list contains A), A puts B back, and the line grows forever — the printout is an endless churn of the same names. Any loop does it: A–B–D–C–A is a merry-go-round with no exit. This is §1's warning made concrete, and it's why the breadcrumb set isn't an optimization — it's the difference between an algorithm and a wandering ghost. (Week 1 would say: finiteness is part of the definition.)
Section 4The maze-walker: depth-first search
Strategy two is the bold one. Forget rings — plunge. At every junction, take the first unexplored hallway and keep going deeper until you hit a dead end (no unvisited neighbors). Then back up — only as far as the last junction that still has an untried hallway — and plunge again. It's exactly how you solve a hedge maze with one hand on the wall. The official name is depth-first search, DFS; we'll call it the maze-walker.
Its bookkeeping is the mirror image of the ripple's. Instead of a line at the door, the maze-walker keeps the trail of forks behind it — the junctions it passed on the way in, newest on top, so "back up" means "return to the most recent fork." That last-in-first-out object is a stack (Week 6 again). And here's the lovely part: recursion keeps that trail for you, for free. Every recursive call is a fork remembered; every return is backing up one fork. You watched the call stack do precisely this in Notes 01's recursion demo:
def maze_walk(friends, person, crumbs=None): """Go deep. When stuck, back up to the last fork and try the next hallway.""" if crumbs is None: crumbs = set() crumbs.add(person) print(person) for buddy in friends[person]: if buddy not in crumbs: maze_walk(friends, buddy, crumbs) # the call stack IS the trail of forks
Same breadcrumbs, same "visit everything once" guarantee — different order, and the order is the whole personality. When does each win?
- The ripple (BFS) when the question is about nearness: shortest route, fewest hops, closest anything. Its ring order is a promise no other order makes.
- The maze-walker (DFS) when the question is "explore everything" or "understand the structure": solving mazes, listing every file in nested folders, and — in §5's close cousin — sniffing out loops. Going deep is cheap on memory when graphs are long and stringy, and recursion makes the code almost insultingly short.
- Cost: identical, and honest. Both explorers touch each dot once (breadcrumbs) and peek down each hallway once from each end. Week 2 language: time grows like dots + links. You cannot do better — you can't claim to have explored a building without touching every room and every hallway.
Now race them. Thirteen dots, plenty of loops, and — notice — a little triangle in the corner that no link reaches. Tap a mode, then tap any dot:
Some things to try:
- Ripple from A and read the colors: B and E light up as ring 1, then C and F, then D, G, I, then H and J — the visit numbers never jump ahead of the rings. That picture is the shortest-path argument.
- Maze-walk from A and watch it bolt: A, B, C, D, H, J — six dots deep before it ever looks back, the trail of forks stacking up behind it. Same thirteen dots by the end (an island is an island either way), order entirely different.
- Switch to Shortest route, tap E, then H. Four links, lit in green. Try to beat it by eye. Then tap A, then K — and meet §6 early.
- Ripple from K. Three dots and done: the mainland might as well not exist. Keep that feeling — it's the whole of §6.
(a) The ripple — "fewest stops" is exactly the ring number, and only BFS promises it. (b) The maze-walker — folders nest like a maze's corridors, and the recursive code writes itself (it's how your file explorer actually does it). (c) Either. "Is there a path" only asks whether the exit gets a breadcrumb, and both explorers reach precisely the same dots — they differ in order, never in territory. If you additionally wanted the shortest way out, (c) becomes (a): take the ripple.
Section 5Getting dressed in the right order
New kind of graph, new superpower. So far our links had no direction — a friendship points both ways. But "socks before shoes" points one way. A graph whose links are arrows is a directed graph, and the everyday version is any to-do list with dependencies: course prerequisites, build systems, recipes, and the oldest scheduling problem in the world — getting dressed.
How do you find such an order? The method you already use every morning, stated out loud:
Look for any task with no remaining prerequisites — nothing left to wait for. Do it, cross it off, which may free up new tasks. Repeat until everything is crossed off — or until you're stuck. (The literature calls this Kahn's algorithm; your morning routine predates the name by some decades.)
And the sting in the tail is the best part. When can this get stuck — tasks remaining, but none of them doable? Only when every remaining task waits on another remaining task, round and round: a loop in the arrows. "A requires B, B requires C, C requires A" — a degree where two courses are each other's prerequisite. No valid order exists, not because we searched badly, but because the demand is self-contradictory. So the dressing method is secretly two tools in one: it produces an order when one exists, and its failure is a proof that a cycle exists. When npm, or make, or your university's registrar prints circular dependency detected — this is the tool that said it, getting stuck on purpose.
Try the morning routine yourself — then curse it:
Two honest notes the demo makes vivid. First, many valid orders usually exist — the wardrobe game will tell you exactly how many its rules allow, and any of them is a full-credit answer; "the" topological order is almost never unique. Second, when the cursed arrow is in place, notice where you get stuck: not immediately, but after the free items run out — which is exactly how real circular-dependency bugs feel. Everything builds fine until suddenly nothing does.
The dressing method in Python — eight lines, stuck-detection included
def get_dressed(tasks, needs): # needs[t] = set of t's prerequisites done = [] while len(done) < len(tasks): ready = [t for t in tasks if t not in done and needs[t] <= set(done)] if not ready: return None # stuck with tasks left = there's a loop done.append(ready[0]) # anything ready is fine — hence many valid orders return done
This is the plain-words method, transcribed. (Production versions keep a count of unmet prerequisites per task so they never rescan the whole list — same idea, faster bookkeeping, and the cost lands back at our familiar "dots + links.")
Four. The coffee chain buy → grind → brew is rigid, but the toast has no arrows touching it, so it can slot into any of the four gaps: before buy, after buy, after grind, or after brew. One graph, four perfectly correct mornings — which is the "many valid orders" point in miniature. (And if toast had needed brew, say, the count would drop to one. Every arrow you add removes freedom.)
Section 6Islands: who can reach whom
One more gift, and it's almost free. In Demonstration 1, rippling from A touched ten dots and rippling from K touched three — and no run ever touched all thirteen. The map isn't one territory; it's two islands. Dots A–J can all reach each other; K, L, M can all reach each other; and no link bridges the gap. Each island is called a connected component, and finding them all takes exactly one idea:
Start anywhere; run a ripple (or a maze-walk — territory is the same either way). Everything it touches is island number 1. Now scan for any dot with no breadcrumb; explore from there — that's island 2. Repeat until every dot has a crumb. The crumbs are shared across all the runs, so each dot and each link still gets touched once: the whole census costs one sweep, dots + links.
def count_islands(friends): crumbs, islands = set(), 0 for person in friends: if person not in crumbs: islands += 1 # an unseen dot = a new island ripple_into(friends, person, crumbs) # §3's ripple, sharing the crumb set return islands
Why care? Because "are these two things connected at all?" is a question the real world asks constantly. Friend circles that never overlap. A power-grid failure that splits a region into cells — which neighborhoods lost contact with the plant is a components question. "Is this maze even solvable" is "do start and exit share an island." Network engineers monitoring whether an outage has partitioned the internet are counting components on a very large, very expensive graph. The algorithm is the one above, every time.
One honest sentence about one-way streets: when the links are arrows, "I can reach you" stops implying "you can reach me" (every follower of a celebrity knows this), so "island" needs more care to define there — a story for a later course; this week's two-way islands are the workhorse case.
Touching all 50 means exactly one island — everything reaches everything, no further runs needed. Touching 49 means at least two: one dot has no crumb, so no chain of links connects it to your start. Do what the census says: explore from the crumbless dot. If it touches only itself, the answer is two islands (a 49 and a 1 — a hermit); it can't touch more than that one dot without contradicting the first run. Notice you never needed to "search for the gap" — the breadcrumbs found it for you.
Section 7Before next session
- Review Notes 11. Then close the page and narrate from memory: the two explorers' bookkeeping (the line vs. the trail), why the ripple's first touch is shortest, and how the dressing method doubles as a cycle detector. If you can also say when a checkbox table beats a friends list, you own the week.
- Project 3 is due next week. Final stretch — bring one concrete blocker to Thursday's work time if you have one. Stuck is normal; silently stuck is the only mistake.
- In the demos: race both explorers from every dot on the map and watch what changes (order) and what never does (territory). Then take the cursed wardrobe and find the other dead end — there's more than one way to get stuck.
- Optional challenge: write ripple with the "who put you in line" jottings and print the actual shortest route between two dots — then check yourself against Demonstration 1's Shortest route mode.
- Optional rabbit hole: the "six degrees of separation" claim is a statement about BFS rings on the human friendship graph. Look up the Bacon number (or the Erdős number) and compute what it is in ripple language. Facebook once measured its whole graph's average: 3.57 rings.
This week the links just existed. Next week they get prices — and we build the cheapest network that still connects everyone: the problem every ISP, power grid, and railroad has had to solve. Two greedy strategies that actually work (a rare luxury, as Week 8 taught you), plus the data structure that makes them almost free: union-find, the fastest island-keeper ever built. Project 3 is due that week. Sessions 22–23.