Graph Traversal: BFS & DFS

This deck covers breadth-first and depth-first search on graphs stored as adjacency lists. It gives a full BFS trace with a queue and shortest-path parent pointers, explains why BFS finds shortest paths only when every edge costs the same, and gives a full DFS trace with discovery and finish times together with the tree, back, forward, and cross edge classification that DFS reveals. It then explains why both run in time proportional to the number of vertices plus edges rather than to the number of vertices squared. It targets four real misconceptions: trusting BFS shortest paths on weighted graphs, forgetting the visited set, assuming quadratic running time, and mixing up which data structure belongs to which traversal.

Subject: CS3000 Algorithms · 140 slides · symbolic lesson

Open the interactive version of this deck · Homework for this lesson

What this lesson covers

The lesson, slide by slide

1. Graph Traversal: BFS & DFS

Title

CS3000 Algorithms · Graphs

Two ways to walk every reachable vertex — one level at a time, or as deep as possible before backing out.

2. What you will be able to do

Objectives

Every graph algorithm you meet this semester — shortest paths, cycle detection, topological sort, connectivity — is built on top of two basic ways of walking a graph. By the end of this lesson you can:

  1. Trace breadth-first search (BFS) on a graph using a queue, level by level, and explain why it finds shortest paths only when every edge costs the same.
  2. Trace depth-first search (DFS) on a graph using recursion or an explicit stack, recording discovery and finish times.
  1. Classify every edge a DFS encounters as a tree, back, forward, or cross edge.
  2. Explain why both BFS and DFS run in time proportional to the number of vertices plus edges — never the square of the vertex count.

3. Toolkit check-in: name them before you look

Concept

Before any new material: cover the screen.

You have named 14 reusable moves so far. Say as many as you can out loud, by number, from memory.

Do not advance until you have actually tried. Getting four of eight is information; skipping the exercise is not.

Here they are. Score yourself.

Today adds no new moves. Every proof in this lesson is built out of the list above. That is the whole point of the list.

The question that starts every proof from here on is not how do I begin. It is which of these applies here?

4. Graphs & Traversal Basics

Section

Section 1

5. Warm-up: what is a graph, in your own words?

Warm-up

Discussion prompt

Before the formal definition: name three things from everyday life that are really graphs, and say what the vertices and edges are in each.

Hint: Anything where things are connected to other things.

Answer:

Road maps: vertices are intersections, edges are roads. Social networks: vertices are people, edges are friendships. Web pages: vertices are pages, edges are links. Notice the last one is directed and the middle one usually is not.

6. What a graph is

Concept

A graph is a set of vertices (the dots, also called nodes) connected by edges (the lines between them). A road map, a social network, and a set of course prerequisites are all graphs.

An edge connecting vertex A and vertex B means there is a direct connection between them. A and B are called neighbors when an edge joins them.

graph — A pair consisting of a set of vertices and a set of edges connecting pairs of vertices.

\[ G = (V, E) \]

7. A graph is vertices joined by edges

Picture it

Animation

Shows: A graph is vertices joined by edges — a rendered Manim animation.

Rendered with Manim.

Takeaway: Six vertices, six edges. Nothing else is assumed.

8. Sort these: which relationships point both ways?

Sorting

Sort into buckets

Decide whether each relationship is naturally symmetric. Your answer determines whether you would model it with a directed or an undirected graph.

Undirected — both ways at once
Alice is Facebook friends with Bob; A two-way street between two intersections
Directed — one way only
Alice follows Bob on a social feed; Page A links to page B; A scheduled flight from one airport to another
undirected
The relationship cannot hold in one direction without holding in the other. Storing it needs each edge recorded twice, once in each vertex's list.
directed
The relationship can hold one way and not the other. Following someone does not make them follow you, and a link does not create a link back. Each edge is stored once, in the source's list only.

9. Directed vs undirected graphs

Concept

In an undirected graph, an edge between A and B can be crossed in either direction — think of a two-way street.

In a directed graph, an edge points one way only, from a source vertex to a target vertex — think of a one-way street. An edge from A to B does not mean there is also one from B to A.

Both BFS and DFS work on either kind of graph. On a directed graph, neighbor means 'vertex reachable by following an outgoing edge.'

10. One-way vs two-way streets

Intuition

Picture a city map. An undirected graph is a city of two-way streets — you can drive from the library to the park and back the same way.

A directed graph is a city with one-way streets. You might drive from the library to the park, but the only way back could be a completely different route — or there might be no way back at all.

11. Trade-off: adjacency list versus adjacency matrix

Trade off

Comparison matrix

Fill in what each representation costs. This choice decides the running time of everything that follows.

Adjacency listAdjacency matrix
Space usedV plus EV squared
Check if one specific edge existsdegree of the vertexconstant
List all neighbours of a vertexdegree of the vertexV

12. Adjacency list representation

Concept

The adjacency list representation stores, for every vertex, a list of its neighbors. It is the standard way to represent a graph for traversal algorithms.

VertexNeighbors (adjacency list)
AB, C
BA, D
CA, D, E

Looking up 'who are A's neighbors' costs time proportional to how many neighbors A has — not to the total number of vertices in the graph. That detail is the whole reason traversal is fast.

13. Predict: why does traversal prefer the list?

Prediction

Predict first

Traversal spends its whole life asking one question: who are this vertex's neighbours? Which representation makes that cheap, and what does that do to the total running time?

  • The list — you read only the neighbours that exist, so the total is V plus E
  • The matrix — constant-time lookups make the total V plus E
  • Neither, both give V squared

Correct: The list. You read only real neighbours, so the totals come to V plus E.

Why: The matrix answers the wrong question fast. Traversal never asks whether one specific edge exists; it asks for all neighbours at once, and the matrix makes you scan a whole row of V entries to find them, most of which are absent. That scan is what turns the bound quadratic.

14. Degree of a vertex

Concept

The degree of a vertex is the number of edges touching it — the length of its adjacency list.

degree — For an undirected graph, the number of neighbors a vertex has.

\[ \sum_{v \in V} \deg(v) = 2|E| \]

We will use this counting fact later to explain exactly why traversal time is proportional to vertices plus edges.

15. Why the degrees sum to twice the edges

Picture it

Animation

Shows: Why the degrees sum to twice the edges — a rendered Manim animation.

Rendered with Manim.

Takeaway: Every edge is counted exactly twice — once from each end.

16. A graph as a map of rooms and doors

Intuition

Picture each vertex as a room, and each edge as a door connecting two rooms. Traversal just means: starting in one room, walk through doors until you have visited every room you can reach.

BFS and DFS are two different strategies for doing that walk. They visit the same reachable rooms in the end — but in a different order, and for different reasons.

17. From wandering a building to an algorithm

Real world

Discussion prompt

You are exploring an unfamiliar building with many rooms and doors, and you want to see every room exactly once. Describe your strategy in plain English, then say what you would have to carry with you to make it work.

Hint: What goes wrong if you keep no record at all?

Answer:

You need two things. A record of rooms already seen, or you will walk in circles forever. And a list of doors noticed but not yet opened, so you can come back to them. Those two are exactly the visited set and the frontier — and the only real difference between BFS and DFS is which end of that frontier you take from.

18. What traversal means

Concept

A traversal systematically visits every vertex reachable from a starting vertex, following edges, and visits each one exactly once.

'Reachable' means there is some path of edges from the start to that vertex. Revisiting a vertex once it is known would waste work — and on a graph with a cycle, it could cause an infinite loop.

19. Something is wrong: this traversal never stops

Anomaly

Predict first

A student writes traversal code with a frontier but no visited set. It runs on a triangle graph and never terminates. Where exactly does the loop come from?

  • Each vertex keeps getting re-added by its neighbours, forever
  • The triangle has an odd number of vertices
  • The frontier is the wrong data structure

Correct: Every vertex keeps being re-added by neighbours that were themselves re-added.

Why: Without a record of what has been seen, visiting a vertex pushes all its neighbours onto the frontier again, including the one you just came from. That neighbour then pushes this vertex back. The frontier never empties, and it has nothing to do with the number of vertices — any graph containing a cycle, or even a single undirected edge, does it.

20. The visited set

Concept

Every traversal keeps a visited set — the vertices already discovered. Before following an edge to a vertex, first check whether it is already in the visited set.

visited set — The collection of vertices a traversal has already discovered. Checking it before visiting a vertex again is what prevents infinite loops on graphs with cycles.

Mark a vertex visited the moment you discover it — not when you finish processing it. Otherwise the same vertex can be queued or pushed more than once before anyone notices.

21. Breadth-First Search

Section

Section 2

22. Step zero: what would exploring level by level require?

Step zero

Discussion prompt

You want to see all rooms one door away before any room two doors away. What discipline must you impose on the frontier to make that happen?

Hint: Think about which waiting item you should serve next.

Answer:

Serve the frontier in the order things arrived — oldest first. Everything one door away is discovered before anything two doors away, so oldest-first means you finish a whole level before starting the next. That discipline is a queue, and the queue is the entire reason BFS finds shortest paths.

23. BFS overview — queue and levels

Concept

Breadth-first search (BFS) explores a graph outward in layers: first the start vertex, then all of its neighbors, then all of their unvisited neighbors, and so on.

BFS uses a queue — a first-in-first-out structure — to track which vertex to explore next. Vertices are explored in the order they were discovered.

queue — A first-in-first-out (FIFO) structure: whatever was added first is removed first. BFS enqueues newly discovered vertices at the back and dequeues from the front.

24. Ripples from a dropped stone

Intuition

Drop a stone in a pond. The ripple reaches every point at distance one before it reaches any point at distance two. That is exactly BFS: it finishes an entire layer before moving to the next one.

The queue is what enforces this ordering. New discoveries go to the back of the line, so the traversal always finishes the current layer before starting the next.

25. Predict: when should a vertex be marked visited?

Prediction

Predict first

There are two plausible moments to mark a vertex visited: when you first put it on the queue, or when you later take it off. Which is correct, and what breaks with the other?

  • When you enqueue it — otherwise it can be enqueued several times before it is ever dequeued
  • When you dequeue it — marking early would skip vertices
  • Either one, they give the same result

Correct: When you enqueue it.

Why: If you wait until dequeue, a vertex with three unvisited neighbours pointing at it gets pushed three times before it is ever processed, and it will be expanded three times over. The queue can swell far beyond V and the running time stops being linear, even though the final answer often still looks right.

26. BFS mechanics

Concept

BFS from a start vertex, step by step:

  1. Mark the start vertex visited and put it in the queue.
  2. Repeatedly take the vertex at the front of the queue.
  3. For each of its neighbors not yet visited: mark it visited and add it to the back of the queue.

Marking a vertex visited the moment it is added to the queue (not when it is later removed) is essential — otherwise the same vertex could be added to the queue more than once.

27. BFS in pseudocode

Concept

A queue, a distance table, and a parent table. The queue is what makes it breadth-first; swap it for a stack and you have written DFS instead.

BFS(G, s)
  for each u in V
    dist[u] = INFINITY
  dist[s] = 0
  Q = queue containing s
  while Q is not empty
    u = Dequeue(Q)
    for each v in Adj[u]
      if dist[v] == INFINITY
        dist[v] = dist[u] + 1
        parent[v] = u
        Enqueue(Q, v)

Line 9 is the visited check, and it does two jobs at once: it stops the algorithm looping forever on a cycle, and it guarantees each vertex is enqueued exactly once, which is what keeps the running time linear.

28. Reading BFS line by line

Notation

Every line of BFS says one thing. Read the line, then read what it does — not the other way round.

Annotate

  • Infinity means 'not yet discovered'. The distance table doubles as the visited set, so no separate structure is needed.
  • A QUEUE — first in, first out. This single choice is the entire difference between BFS and DFS.
  • Dequeue takes the oldest vertex, so everything at distance k is processed before anything at distance k plus one.
  • The visited check. Without it, a cycle makes this loop forever; with it, each vertex is enqueued at most once.
  • One more hop than the vertex it was found from. This is why BFS distances are correct only when every edge costs the same.
  • The parent pointers, taken together, form the shortest-path tree — that is the real output, not just the distances.

29. Step BFS yourself

Invariant

The queue always holds vertices from at most two consecutive layers, with all of the nearer layer ahead of the further one. That ordering is exactly why distances come out right.

Step through it

Before each dequeue, name what is in the queue and at what distance.

  1. Line 4: the source is at distance 0
  2. Line 7: dequeue s
  3. Line 10: a is one hop out
  4. Line 10: and so is b
  5. Line 7: dequeue a, the oldest in the queue
  6. Line 10: c is two hops out
  7. Line 9: a's edge to b finds it already known
  8. Line 10: d is two hops out
  9. Line 10: e is three hops
  10. Line 9: d's edge to e is too late — e is already settled

30. BFS discovers the graph one layer at a time

Picture it

Animation

Shows: BFS executing: the current line of pseudocode is highlighted while the data it touches changes.

Rendered with Manim.

Takeaway: A queue processes every vertex at distance k before any at distance k plus one, so first discovery is the fewest-hops discovery.

31. Predict: what do you store to recover the actual path?

Prediction

Predict first

BFS can tell you the distance to every vertex. To also report the route, what is the least you must store?

  • One pointer per vertex, to whichever vertex discovered it
  • The full path to every vertex
  • The entire queue history

Correct: One pointer per vertex, pointing at whoever discovered it.

Why: Storing full paths would cost far more space and duplicate almost everything, since paths share long prefixes. A single parent pointer per vertex is enough: follow the chain backwards from the target to the source and reverse it. Those pointers together form a tree, which is why it is called the shortest-path tree.

32. BFS parent pointers build a shortest-path tree

Concept

While enqueuing a new vertex, record which vertex discovered it — its parent in the traversal. Following parent pointers backward from any vertex to the start retraces a path.

parent pointer — For a vertex discovered during traversal, the neighbor being processed when it was first found. Chaining parent pointers back to the start reconstructs the path taken to reach it.

Because BFS explores layer by layer, that reconstructed path always turns out to be a shortest path in terms of number of edges — argued on the next few slides.

33. The parent pointers form a shortest-path tree

Picture it

Animation

Shows: The parent pointers form a shortest-path tree — a rendered Manim animation.

Rendered with Manim.

Takeaway: One pointer per vertex is enough to rebuild every route.

34. Step the BFS yourself — and watch what never changes

Invariant

Step through it

Before each step, say which vertex comes off the queue next. Then check: is the queue ever holding two different distances that differ by more than one?

  1. Start: only the source is known, sitting alone in the queue.
  2. Its neighbours enter together. The queue holds one distance value.
  3. A level-2 vertex appears while a level-1 vertex still waits.
  4. The level-1 vertices are gone; only level 2 remains.
  5. Level 3 begins to appear while level 2 finishes.
  6. The invariant held the whole way: the queue never held distances differing by more than one.

35. Full BFS trace on a 6-vertex graph

Worked example

Figure (svg): An undirected graph with six vertices A through F. Edges: A-B, A-C, B-D, C-D, C-E, D-F, E-F.

Run BFS starting at vertex A. Adjacency lists, alphabetical order: A: B, C. B: A, D. C: A, D, E. D: B, C, F. E: C, F. F: D, E.

Initialize

Why: Mark A visited and enqueue it alone. Queue: [A].

Dequeue A, examine its neighbors

Why: A's neighbors are B and C, both unvisited. Mark both visited and enqueue them in that order. Queue becomes [B, C].

Dequeue B, examine its neighbors

Why: B's neighbors are A (visited) and D (unvisited). Enqueue D. Queue becomes [C, D].

Dequeue C, examine its neighbors

Why: C's neighbors are A (visited), D (visited), and E (unvisited). Enqueue E. Queue becomes [D, E].

Dequeue D, examine its neighbors

Why: D's neighbors are B (visited), C (visited), and F (unvisited). Enqueue F. Queue becomes [E, F].

Dequeue E, then F — nothing new

Why: E's neighbors (C, F) are both already visited, and F's neighbors (D, E) are both already visited. The queue empties and BFS ends.

Verify the full trace against the queue and visited-set history

Why: Every vertex was enqueued exactly once and every edge was examined once from each endpoint that processed it, matching the table.

StepDequeuedNeighbors examinedNewly enqueuedQueue afterVisited after
1(start)-A[A]{A}
2AB, CB, C[B, C]{A, B, C}
3BA, DD[C, D]{A, B, C, D}
4CA, D, EE[D, E]{A, B, C, D, E}
5DB, C, FF[E, F]{A, B, C, D, E, F}
6EC, F-[F]{A, B, C, D, E, F}
7FD, E-[]{A, B, C, D, E, F}

36. Guess the invariant before you are shown it

Hypothesis

Predict first

From the trace you just stepped through: what stayed true about the queue at every single moment?

  • It only ever held vertices from at most two consecutive levels
  • It only ever held vertices from one level at a time
  • It always held exactly as many vertices as the level it was working on

Correct: At most two consecutive levels, never more.

Why: This is the invariant the whole correctness proof rests on. While you drain level k you are appending level k plus one behind it, so the queue straddles exactly two levels. It cannot straddle three, because level k plus two is only reachable once level k plus one is being processed, and by then level k is gone.

37. The Layer Induction skeleton

Concept

Every proof of this kind has the same five or six moves in the same order. The order is not something you rediscover each time.

It is on the right. It will stay on the right through the worked examples that follow.

Why this matters: the structure is now handled. You are not spending working memory on what comes next — you are spending all of it on the one hard step.

Step 1 decides whether this works at all. A claim about all vertices has nothing to induct on; the same claim indexed by distance almost proves itself.

38. Decision point: prove BFS finds shortest paths

Intuition

What move should we make next?

The claim, for an unweighted graph:

\[ \text{BFS assigns every reachable } v \text{ its true distance } \delta(s, v) \]

Every trace you have run agrees with it. Tracing more graphs will not prove it.

The claim is about all vertices at once, which is not something you can induct on directly. What do you index it by, and which move is that?

_Look at your toolkit. Say a move number out loud before this slide advances._ A wrong guess is useful. A silent guess is not.

39. Explain the step: why can't BFS overshoot?

Explain it to yourself

Discussion prompt

Suppose BFS assigns some vertex a distance of 5, but there is genuinely a path of length 3 to it. Explain why that is impossible — walk the contradiction out step by step.

Hint: Look at the vertex just before it on that shorter path.

Answer:

If a path of length 3 exists, the vertex just before the target on it sits at distance 2 or less. BFS processes every vertex at distance 2 before any at distance 4 or 5, and when it processed that neighbour it would have discovered the target and given it distance 3. So the target could never still be undiscovered when distance 5 was being handed out. The assumption contradicts the level ordering.

40. Why BFS discovers vertices in nondecreasing distance order

Concept

Claim: BFS from a start vertex discovers vertices in nondecreasing order of their true shortest distance, measured in number of edges, from the start — as long as every edge counts as one step.

Reasoning: the queue always holds vertices from at most two consecutive layers. Every vertex at distance k is enqueued only while processing layer k-1, and the queue's first-in-first-out order guarantees all of layer k-1 is dequeued before any of layer k.

\[ \text{distance}(v) = \text{distance}(\text{parent}(v)) + 1 \]

So the very first time BFS reaches a vertex is guaranteed to be by the shortest possible route in edges — exactly why the parent pointers trace out a shortest path.

41. BFS spreads outward, level by level

Picture it

Animation

Shows: BFS spreads outward, level by level — a rendered Manim animation.

Rendered with Manim.

Takeaway: Everything one hop away is finished before anything two hops away.

42. The move we just made, named

Concept

The move: #5 (Peel one off), then #6 (Substitute the hypothesis).

Index the claim by distance, not by vertex

Why: Restate it as: every vertex at true distance k is discovered on layer k. Now there is a number to induct on. This restatement is the whole trick.

Base case: the source

Why: Distance 0, discovered first, assigned 0.

Peel one off: a vertex at distance k+1

Why: Its shortest path has some vertex u at distance k immediately before it. By the hypothesis u is on layer k, and BFS scans every neighbour of u when it dequeues u.

\[ \delta(s, v) = k + 1 \;\Longrightarrow\; v \text{ is discovered while scanning layer } k \]

Notice how the queue does not appear in the argument at all. The queue is the implementation of layer order; the proof is about the layers.

43. Work backwards: rebuild the path from the pointers

Reverse engineer

Fill in the blanks

The parent pointers say: F came from D, D came from B, B came from A, and A is the source. Complete the reconstruction loop.

path = []
node = target
while node is not source:
path.append(node)
node = parent[node]
path.append(source)
path.reverse()

Why: Following parent pointers walks from the target back towards the source, so the list comes out backwards and must be reversed at the end. The loop stops at the source because the source is the one vertex with no parent — it was never discovered by anybody.

44. Reconstruct the shortest path from A to F

Worked example

Using the parent pointers recorded during the trace on the previous graph, find the shortest path from A to F.

Record each vertex's parent from the trace

Why: B and C were discovered while processing A, so their parent is A. D was discovered while processing B. E was discovered while processing C. F was discovered while processing D.

VertexParentDistance
A-0
BA1
CA1
DB2
EC2
FD3

Walk parent pointers backward from F

Why: F's parent is D, D's parent is B, B's parent is A. Reading that chain in reverse gives the path from A to F.

\[ A \to B \to D \to F \]

Verify the path length matches F's recorded distance

Why: The path A to B to D to F has 3 edges, matching F's distance of 3. No shorter path exists, since BFS discovers vertices in nondecreasing distance order and F was first reached at distance 3.

\[ |A \to B \to D \to F| = 3 = \text{distance}(F)\ \checkmark \]

45. Step the reconstruction backwards yourself

Pattern

Step through it

Before each step: which vertex does the pointer send you to next? Notice the path comes out backwards.

  1. Following parent pointers always starts at the target, never the source.
  2. Each step climbs one level closer to the source.
  3. Still climbing — every vertex on this chain sits one level up.
  4. The source is the one vertex with no parent, which is what ends the loop.
  5. Reverse it and you have the path. That final reversal is the step most people forget.

46. What feels wrong about running BFS on a weighted graph?

Intuition

What feels wrong about this?

A graph with two routes from s to t: one edge of weight 10, or two edges of weight 1 each.

\[ s \to t \; (\text{cost } 10) \qquad \text{versus} \qquad s \to u \to t \; (\text{cost } 2) \]

BFS reports the one-edge route, because it is one hop.

_Plain English only. No notation, no algebra. Just say what bothers you._

The feeling: BFS is counting hops, and nobody told it that the hops have different prices.

That feeling is the proof. It is not a substitute for the proof — it is the thing the proof writes down.

So the fix is not to patch BFS — it is to notice that the proof used distance equals number of edges in the induction step. Change what distance means and the induction step is simply false. That is Dijkstra's whole reason to exist, two lessons from now.

47. Where BFS gives the wrong answer

Picture it

Animation

Shows: Where BFS gives the wrong answer — a rendered Manim animation.

Rendered with Manim.

Takeaway: BFS is not wrong — it is answering a different question.

48. Break it: find a graph where BFS gives the wrong answer

Counterexample

Discussion prompt

Claim: BFS always finds the cheapest route. Draw a small weighted graph where BFS returns a route that is genuinely more expensive than another available one.

Hint: Two vertices, two routes: one hop that is expensive, or several hops that are each cheap.

Answer:

Take A to D directly with weight 100, and also A to B to C to D with each edge weight 1. BFS counts hops, so it reports the one-hop route as best, at total cost 100. The three-hop route costs 3. BFS is not wrong about anything it claims — it minimises the number of edges — it is simply answering a different question from the one weights ask.

49. Trap: BFS only guarantees shortest paths in unweighted graphs

Trap

The trap

A student runs BFS on a graph whose edges have different costs (a weighted graph) and assumes the BFS tree still gives the cheapest paths.

EdgeWeight
S - T10
S - A1
A - T1

Treat 'fewest edges' as 'cheapest'

Why: BFS from S reaches T directly in one hop (the edge S-T), so a student concludes the shortest path from S to T is that direct edge, at total cost 10.

\[ \text{BFS conclusion: } S \to T,\ 1 \text{ hop, cost } 10 \]

The fix

BFS only counts edges, never their weights. On a weighted graph you must compare total weight, not hop count.

EdgeWeight
S - T10
S - A1
A - T1

Compare total weight of every path

Why: The direct edge S-T costs 10. The two-hop path S-A-T costs 1+1 = 2, which is cheaper despite using more edges.

\[ S \to A \to T:\ 1+1=2 \ < \ 10 \]

Use the right tool for weighted graphs

Why: BFS's shortest-path guarantee holds only when every edge has the same cost — an unweighted (or unit-weight) graph. For graphs with different edge weights, use an algorithm built for weights, such as Dijkstra's algorithm.

50. Debug this: it returns, but the answer is wrong

Error analysis

Annotate

This BFS terminates and looks reasonable. It reports the wrong distances. Find the line that does it.

  • Fine. The source starts at distance zero and seeds the queue.
  • Fine. Taking from the left is what makes this breadth-first rather than depth-first.
  • Fine. A vertex gets a distance only the first time it is seen, which is the correct one.
  • Here is the bug. The append sits outside the guard, so every neighbour is enqueued on every visit, including ones already assigned a distance.
  • The distances happen to come out right on simple graphs, which is what makes this so hard to catch. What is actually broken is the running time: the queue grows without the linear bound. Move the append inside the if.

51. Trap: forgetting the visited set causes infinite loops

Trap

The trap

On a 3-vertex cycle, a student writes BFS without ever checking whether a vertex has already been visited.

\[ A - B, \quad B - C, \quad C - A \]

Enqueue every neighbor, visited or not

Why: Starting from A: enqueue B and C. Dequeuing B re-enqueues A and C, since nothing marks them as already handled. Dequeuing C does the same.

DequeuedQueue after (no visited check)
A[B, C]
B[C, A, C]
C[A, C, A, B]

The queue keeps growing forever

Why: Every vertex keeps re-adding its neighbors because nothing is ever recognized as 'already seen.' The queue never empties, and the loop never terminates.

The fix

Mark each vertex visited the instant it is enqueued, and never enqueue a vertex already in the visited set.

\[ A - B, \quad B - C, \quad C - A \]

Check the visited set before enqueuing

Why: Starting from A: mark A visited, enqueue it. Dequeuing A finds B and C unvisited, so mark and enqueue both.

DequeuedNeighbors checkedNewly enqueuedVisited after
(start)-A{A}
AB, CB, C{A, B, C}
BA, C-{A, B, C}
CA, B-{A, B, C}

The traversal terminates correctly

Why: Once B and C are dequeued, every one of their neighbors is already visited, so nothing new is enqueued. The queue empties after exactly 3 dequeues — one per vertex.

52. What is missing? BFS only found four of the six

Missing information

Discussion prompt

You run BFS from vertex A on a six-vertex graph and it reports distances for only four vertices. No bug is present. What does that tell you about the graph, and what must you change to visit everything?

Answer:

The graph is disconnected — the other two vertices are in a component with no route from A at all. A single BFS explores exactly one component by design. To cover the whole graph, loop over all vertices and start a fresh BFS from any that is still unvisited. The number of times you have to start is the number of connected components.

53. BFS on a disconnected graph

Concept

Not every graph is fully connected. If some vertices cannot be reached from the start at all, a single BFS run never visits them — the queue empties while vertices remain unvisited.

To visit every vertex in the whole graph, run BFS again from any unvisited vertex, repeating until none remain. Each run discovers one connected component.

connected component — A maximal set of vertices that can all reach each other. Running a full traversal from every yet-unvisited vertex, one component at a time, discovers all of them.

54. BFS discovers two connected components

Worked example

Graph: vertices A, B, C, D with edges A-B and C-D only. Run BFS, restarting whenever vertices remain unvisited.

\[ A - B, \quad C - D \]

Run BFS from A

Why: A's only neighbor is B. Enqueue and visit both. B's only neighbor, A, is already visited, so this run ends with {A, B} visited and C, D untouched.

RunStartVisits in this run
1AA, B

C is unvisited, so start a new run from C

Why: C's only neighbor is D. Enqueue and visit both. This second run discovers the second component, {C, D}.

RunStartVisits in this run
1AA, B
2CC, D

Verify every vertex was visited exactly once

Why: Together the two runs visited A, B, C, D — all four vertices, each exactly once — revealing two separate connected components: {A, B} and {C, D}.

55. Watch the restart: one BFS cannot leave its component

Invariant

Step through it

Watch which vertices are reachable at each stage. What has to happen before the second group is ever touched?

  1. One source, one component — that is all a single BFS can ever see.
  2. The queue empties with D and E still untouched. This is not a bug.
  3. Only the outer loop over all vertices can reach the second group.
  4. The invariant: the number of restarts equals the number of connected components.

56. Put the BFS steps in order

Ranking

Put in order

Arrange these into the correct BFS loop body order.

  1. Take the front vertex off the queue
  2. Look at each of its neighbours in turn
  3. Skip any neighbour already marked visited
  4. Mark the new neighbour visited and set its distance and parent
  5. Add the new neighbour to the back of the queue

Why: Marking must happen before enqueueing, and both must sit behind the visited check. Getting this order wrong is what produces the duplicate-enqueue bug that silently destroys the linear running time while often leaving the distances correct.

57. The BFS procedure

Pattern

1. Mark the start vertex visited and enqueue it alone

Why: The queue always begins with exactly one vertex — the source.

2. Dequeue the front vertex and look at its neighbors

Why: Processing the front of the queue guarantees the current layer finishes before the next one begins.

3. Mark each unvisited neighbor visited and enqueue it, recording its parent

Why: Marking on discovery (not on dequeue) is what prevents duplicate enqueues. Recording the parent builds the shortest-path tree.

4. Repeat until the queue is empty; restart from any unvisited vertex if needed

Why: An empty queue means the current component is fully explored. Restarting reaches every remaining component.

58. The invariant the whole proof rests on

Picture it

Animation

Shows: The invariant the whole proof rests on — a rendered Manim animation.

Rendered with Manim.

Takeaway: A third level cannot appear until level k is already gone.

59. Scale it up: what happens on a very wide graph?

Scale up

Step through it

Watch the queue size as the branching factor grows. Predict the peak before stepping.

  1. One vertex to start.
  2. Its two children arrive together.
  3. Each of those contributes two more; the queue holds a whole level.
  4. BFS holds an entire level in memory at once. On a wide graph that level is enormous, which is the practical reason to reach for DFS instead.

60. Check yourself: the BFS queue

Check

Run BFS starting at vertex A on the graph below (same graph as the earlier trace).

Figure (svg): The same undirected six-vertex graph A through F used in the BFS trace.

Check your understanding

After A is dequeued and its neighbors B and C are enqueued, B is dequeued next. B's only unvisited neighbor is D. What is the queue immediately after processing B?

  • A. [C, D] (correct)
  • B. [B, C, D]
  • C. [D]
  • D. [D, C]

Answer: A

Why: Before processing B the queue was [B, C] with B at the front. Dequeuing B removes it from the front, leaving [C]. B's unvisited neighbor D is then enqueued at the back, giving [C, D].

Why B tempts people
This keeps B in the queue after it was dequeued, as if items were only added and never removed — that turns the queue into a growing list instead of a FIFO structure.
Why C tempts people
This drops C from the queue, as if enqueuing D somehow replaced the rest of the queue instead of adding to its back.
Why D tempts people
This places the newly enqueued D ahead of C, treating the queue as last-in-first-out (a stack) instead of first-in-first-out.

61. Depth-First Search

Section

Section 3

62. Predict: change one line and BFS becomes what?

Prediction

Predict first

Take working BFS and change only the frontier discipline from take-oldest to take-newest. What does the algorithm become?

  • Depth-first search
  • Still breadth-first, but slower
  • It stops terminating

Correct: Depth-first search.

Why: The two algorithms are the same program with a different container. Oldest-first is a queue and spreads outward level by level; newest-first is a stack and plunges along one path until it dead-ends. Everything else — the visited set, the neighbour loop, the V plus E bound — is untouched.

63. DFS overview — go deep first

Concept

Depth-first search (DFS) explores as far as possible along one path before backing up. From the current vertex it plunges into an unvisited neighbor immediately, rather than finishing all neighbors first.

DFS can be written recursively — visit a vertex, then recursively visit each unvisited neighbor — or iteratively with an explicit stack. Both produce the same style of traversal: deep, then back out.

stack — A last-in-first-out (LIFO) structure: whatever was added most recently is removed first. DFS's recursive call stack behaves exactly like an explicit stack of 'vertices left to finish.'

64. Match each strategy to how a person would search

Analogy

Match the pairs

Match the search strategy to the human behaviour it mimics.

  • bfs. Breadth-first search
  • dfs. Depth-first search
  • sweep. Checking every room on this floor before taking any stairs
  • plunge. Following one corridor to its dead end, then backing up to the last unexplored door

Why: The behaviours differ only in what you do when you notice several unexplored doors at once. BFS notes them all and comes back in the order noticed; DFS walks straight through the most recent one and postpones the rest until it is forced to back up.

65. Maze exploration with backtracking

Intuition

Imagine exploring a maze with a ball of string. You commit to one hallway and follow it as far as it goes, only turning back when you hit a dead end or a room already visited.

That backing-up is called backtracking. Each retreat returns to the most recent junction with an unexplored hallway — exactly what a stack (or recursion) remembers for you automatically.

66. Decode the two timestamps

Notation

Annotate

DFS stamps every vertex twice. Take the notation apart before using it.

  • Discovery. Stamped the moment you walk into the room and mark it, before looking at any of its doors.
  • Finish. Stamped only after every door out of this room has been fully explored, recursively. This is much later than discovery.
  • Gray means on the current path. Discovered but not finished — you are still somewhere below it in the recursion, and you can still walk back up to it.
  • Black means done. Finished entirely. Nothing below it remains unexplored.
  • Every timestamp is distinct, and each vertex uses exactly two, so the counter runs to twice the number of vertices.

67. Discovery time and finish time

Concept

DFS keeps a running clock. Each vertex gets a discovery time — when it is first visited — and a finish time — when its recursive call, having explored everything reachable through it, returns.

discovery time — The tick of DFS's clock at the moment a vertex is first reached and marked visited.

finish time — The tick of DFS's clock at the moment all of a vertex's neighbors have been fully explored and its recursive call is about to return.

Every vertex's interval from discovery to finish is either completely nested inside another vertex's interval, or completely separate from it — never partially overlapping. That fact is what lets discovery/finish times classify every edge.

\[ [\,d[u],\, f[u]\,] \text{ and } [\,d[v],\, f[v]\,] \text{ are nested or disjoint} \]

68. DFS in pseudocode

Concept

The same traversal with the queue replaced by recursion, plus two clocks. Those clocks are what turn DFS from a way of visiting vertices into a tool that detects cycles and orders a DAG.

DFS-VISIT(G, u)
  time = time + 1
  d[u] = time
  color[u] = GREY
  for each v in Adj[u]
    if color[v] == WHITE
      parent[v] = u
      DFS-VISIT(G, v)
    else if color[v] == GREY
      report a back edge, so G has a cycle
  color[u] = BLACK
  time = time + 1
  f[u] = time

Grey means 'entered but not finished' — still on the recursion stack. So an edge into a grey vertex points back to one of your own ancestors, and an ancestor plus the path down to you is a cycle. That is the whole cycle test.

69. Reading DFS line by line

Notation

Every line of DFS says one thing. Read the line, then read what it does — not the other way round.

Annotate

  • Discovery time: the tick when this vertex was first entered.
  • Grey means still on the stack. The three colours are the difference between DFS as a traversal and DFS as an analysis tool.
  • White means untouched, so this is a tree edge — go deeper immediately, before looking at any other neighbour.
  • Grey means an ancestor of the current vertex. A back edge to an ancestor IS a cycle, and this is how DFS detects one.
  • Black means finished: this vertex and everything reachable from it are fully explored. An edge to black is never a cycle.
  • Finish time. Sorting by decreasing finish time is exactly a topological order, which is the next lesson.

70. Step DFS yourself

Invariant

The grey vertices, at any moment, are precisely the path from the root down to where you are standing. That is why an edge into a grey vertex always closes a cycle.

Step through it

At each step, list the grey vertices and check they form a single path.

  1. Line 3: enter u at tick 1
  2. Line 8: v is white, so go deeper at once
  3. Line 8: deeper again
  4. Line 8: and again
  5. Line 9: x has an edge to v
  6. Line 10: v is GREY — still on the stack. That is a cycle
  7. Line 13: x finishes first: nothing left below it
  8. Line 13: then w
  9. Line 8: back at v, one neighbour left
  10. Line 13: u finishes last, as the root always does

71. DFS goes deep, and grey marks the path back

Picture it

Animation

Shows: DFS executing: the current line of pseudocode is highlighted while the data it touches changes.

Rendered with Manim.

Takeaway: Grey means still on the stack, so an edge into a grey vertex is a back edge — and a back edge is a cycle.

72. Probe the colours: what does each one rule out?

Definition probe

Sort into buckets

You are standing at vertex u and looking down an edge at vertex v. Sort each observation by what colour v must be.

White
v has never been reached at all
Gray
v is still open, sitting above you on the current path; walking to v would close a cycle back onto the current path
Black
v was fully explored some time ago
white
Untouched. Following this edge discovers v for the first time, which makes it a tree edge.
gray
Discovered but unfinished, so v is an ancestor of where you are standing right now. An edge to a gray vertex is a back edge, and it is exactly the signal that a cycle exists.
black
Completely finished before you got here. The edge leads into territory already explored, making it a forward or cross edge, and it can never indicate a cycle.

73. Vertex coloring — white, gray, black

Concept

DFS tracks each vertex's status with a color. White means not yet discovered. Gray means discovered but not yet finished — still open on the current path. Black means finished — fully explored.

A vertex is gray for the entire stretch between its discovery time and its finish time. That is exactly the set of vertices currently on the path from the start down to where DFS is right now.

74. Traffic-light analogy for DFS colors

Intuition

White is a light that has not turned on yet — untouched. Gray is a yellow light — in progress, still active, still on your current route. Black is a light already passed — fully handled, you will not return to it.

When DFS looks at an edge to a gray vertex, it has found a way back to somewhere still 'in progress' on the current path — that is exactly the signature of a cycle.

75. Step the DFS — watch the gray chain

Invariant

Step through it

Before each step, predict which vertex turns gray next. Then watch the gray vertices: do they ever fail to form a single unbroken path?

  1. The source turns gray and stays gray until everything below it is done.
  2. Each step down extends the chain by one.
  3. Three deep now — this chain is literally the recursion stack.
  4. A dead end turns black and the chain shortens.
  5. After backing up, a new branch extends the chain again.
  6. The invariant held: the gray vertices always formed exactly one unbroken path from the root to where you were standing.

76. Full DFS trace with discovery and finish times

Worked example

Figure (svg): A directed graph with six vertices. Tree edges A to B, A to C, B to D, C to F, D to E form the DFS tree. A dashed forward edge runs from A to D, a dashed cross edge runs from C to D, and a curved dashed back edge runs from E up to B.

Run DFS starting at A on the directed graph below. Adjacency lists in visiting order: A: B, C, D. B: D. C: D, F. D: E. E: B. F: none.

Discover A

Why: Clock starts at 1. Mark A gray and set its discovery time. A's first unvisited neighbor is B.

\[ d[A]=1 \]

Discover B, then D, then E

Why: Following the first unvisited neighbor each time: A leads to B, B leads to D, D leads to E.

\[ d[B]=2,\ d[D]=3,\ d[E]=4 \]

E has no unvisited neighbors — finish E, then D, then B

Why: E's only neighbor B is already gray, so nothing new to explore from E. E finishes. D's only neighbor was E, now finished, so D finishes next. B's only neighbor was D, now finished, so B finishes last of the three.

\[ f[E]=5,\ f[D]=6,\ f[B]=7 \]

Back at A, discover C, then F

Why: A's next neighbor is C. C's first unvisited neighbor is F; F has no neighbors, so it finishes immediately.

\[ d[C]=8,\ d[F]=9,\ f[F]=10 \]

C has no more unvisited neighbors — finish C, then finish A

Why: C finishes once F is done. A has now explored all three neighbors (B, C, D — D was already visited), so A finishes last.

\[ f[C]=11,\ f[A]=12 \]

Verify every interval nests or is disjoint as required

Why: B's interval [2,7] contains D's [3,6], which contains E's [4,5] — properly nested, since B, D, E lie on one path. C's interval [8,11] contains F's [9,10] and is entirely disjoint from D's [3,6] and B's [2,7], since C's subtree was explored after they finished.

VertexDiscoveryFinish
A112
B27
C811
D36
E45
F910

77. Two stamps per vertex

Picture it

Animation

Shows: Two stamps per vertex — a rendered Manim animation.

Rendered with Manim.

Takeaway: Gray means still open — still somewhere below you on the path.

78. Translate: recursion into an explicit stack

Translation

Match the pairs

Match each piece of the recursive DFS to what it becomes when you write the loop with an explicit stack.

  • call. Making a recursive call
  • ret. Returning from a call
  • frames. The chain of active call frames
  • push. Pushing a vertex onto the stack
  • pop. Popping and moving on to the next item
  • gray. The gray vertices currently on the stack

Why: Recursion is not a different algorithm, it is a stack the language manages for you. Writing the stack yourself makes that explicit and is what saves you when the graph is deep enough to blow the call stack.

79. DFS mechanics — recursion mirrors an explicit stack

Concept

Recursive DFS on a vertex does three things: mark it visited, then recursively call itself on each unvisited neighbor, then return (finish) once all neighbors are handled.

An explicit-stack version does the same job without function calls: push a vertex, and when it is popped, mark it visited and push its unvisited neighbors. The stack's top always holds the most recently discovered, not-yet-finished vertex — exactly what the recursive call stack tracks automatically.

80. The same graph traced with an explicit stack

Worked example

Trace an explicit-stack DFS on the same 6-vertex graph from the BFS example, starting at A. Mark a vertex visited the moment it is pushed, so it is never pushed twice.

Push A, then pop and process it

Why: Stack starts as [A]. Popping A marks it visited (order 1) and pushes its unvisited neighbors B and C, in reverse order so B ends on top: stack becomes [C, B].

Pop B, then D

Why: B is visited (order 2); its only unvisited neighbor is D, pushed, giving [C, D]. Popping D visits it (order 3) and pushes its unvisited neighbor F, giving [C, F].

Pop F, then E

Why: F is visited (order 4); its only unvisited neighbor is E, pushed, giving [C, E]. Popping E visits it (order 5); both of E's neighbors (C, F) are already visited, so nothing is pushed, leaving [C].

Pop C — traversal ends

Why: C is visited (order 6); all of its neighbors (A, D, E) are already visited, so nothing is pushed. The stack is now empty and every vertex has been visited.

Verify the discovery order differs from BFS on the same graph

Why: Stack-based DFS visited A, B, D, F, E, C — plunging down one path before backing out — while BFS on the identical graph visited A, B, C, D, E, F in level order. Same graph, same start, genuinely different orders, because a queue and a stack hand back vertices in opposite orders.

OrderBFS (queue)DFS (stack)
1AA
2BB
3CD
4DF
5EE
6FC

81. Watch the stack hold exactly what recursion held

Invariant

Step through it

Compare each frame to the recursive trace you just saw. What is on the stack at every moment?

  1. Pushing is calling.
  2. Every push is one more active frame.
  3. The stack IS the call stack, written out where you can see it.
  4. Popping is returning.
  5. Back up, then down a different branch — exactly what backtracking means.
  6. Empty stack, no active frames. The two versions are the same algorithm.

82. Two of these hold. One does not.

Two truths and a lie

Sort into buckets

Sort each claim by whether it is actually true.

True
BFS and DFS visit exactly the same set of vertices from a given source; Both run in time proportional to vertices plus edges
Not true
BFS and DFS visit them in the same order
true
Reachability does not care how you explore, and both algorithms touch each vertex once and each edge once, so both are linear in the size of the graph.
false
Order is the one thing that genuinely differs, and it is the whole point of having two algorithms. BFS goes level by level; DFS plunges to a dead end first.

83. Trap: confusing queue-for-BFS with stack-for-DFS

Trap

The trap

A student wants to run BFS but implements the frontier with a stack instead of a queue — popping the most recently added vertex instead of the oldest.

OrderIntended (BFS, queue)Actual (used a stack)
1AA
2BB
3CD

Use last-in-first-out order by mistake

Why: With a stack, the vertex added most recently (D, discovered while processing B) is explored before C, which was discovered earlier while processing A. The traversal plunges deep instead of spreading level by level, and it stops being BFS at all.

The fix

BFS needs first-in-first-out order — a queue — so the entire current layer finishes before the next layer starts.

OrderBFS (queue)
1A
2B
3C

Use a queue for BFS, a stack or recursion for DFS

Why: A queue processes discoveries in the order they arrived, preserving layers and the shortest-path guarantee. A stack processes the newest discovery first, which is what makes DFS plunge deep — correct for DFS, wrong for BFS.

Match the structure to the guarantee you need

Why: Need shortest paths by edge count? Use a queue (BFS). Need to explore deeply, find cycles, or compute discovery/finish times? Use a stack or recursion (DFS). Swapping them changes both the order and the guarantees.

84. Fill the middle of the DFS routine

Fill the middle

Fill in the blanks

The first and last lines are given. Supply the middle.

def visit(u):
color[u] = "gray"
d[u] = next_time()
for v in adj[u]:
if color[v] == "white":
parent[v] = u
visit(v)
color[u] = "black"
f[u] = next_time()

Why: The vertex turns gray on entry and black only after the loop, which is exactly what makes the gray set equal to the current path. Recursing only into white neighbours is what keeps each vertex visited once and preserves the linear bound.

85. The DFS procedure

Pattern

1. Mark the start vertex visited (discovered) and open it

Why: Recursively, this is entering the function call; iteratively, this is popping it from the stack.

2. Visit its first unvisited neighbor and recurse (or push it)

Why: Go as deep as possible before considering any sibling neighbor.

3. When no unvisited neighbors remain, mark the vertex finished and back out

Why: This return step produces the finish time and lets the previous call continue with its next neighbor.

4. Repeat from any unvisited vertex until none remain

Why: Just like BFS, one call only reaches one connected component; restart to cover the rest of the graph.

86. The gray vertices are the call stack

Picture it

Animation

Shows: The gray vertices are the call stack — a rendered Manim animation.

Rendered with Manim.

Takeaway: Never a scattered set — always one unbroken chain.

87. Where does the time actually go?

Cost model

Annotate

Point at the lines that dominate the running time — not the ones that look busiest.

  • V iterations, but the body is nearly free for vertices already coloured. This loop alone contributes V.
  • Constant work, done exactly once per vertex across the whole run. Contributes V, not V times anything.
  • This is where the time goes. It runs degree-of-u times for each u, and the degrees over all vertices sum to twice the edge count.
  • So the total is V from touching each vertex plus E from touching each edge — the V plus E bound. The nesting looks quadratic and is not, because the inner loop's length varies and its total across all vertices is bounded by the edge count.

88. Check yourself: reading discovery/finish times

Check

In the DFS trace above, vertex B has discovery/finish times (2, 7) and vertex D has discovery/finish times (3, 6).

Check your understanding

What does this pair of intervals tell you about the relationship between B and D?

  • A. D is a descendant of B in the DFS tree — D's interval is nested inside B's. (correct)
  • B. B is a descendant of D in the DFS tree — B's interval is nested inside D's.
  • C. B and D are unrelated, connected only by a cross edge.
  • D. There must be a back edge directly from D to B.

Answer: A

Why: D's interval [3,6] falls entirely inside B's interval [2,7]: 2 is less than 3, and 6 is less than 7. Nested intervals mean D was discovered and finished while B was still open — the signature of D being a descendant of B in the DFS tree.

Why B tempts people
This reverses the nesting direction. The vertex with the wider interval, the one that started earlier and ended later, is the ancestor, and here that is B, not D.
Why C tempts people
Cross edges correspond to disjoint intervals, where neither contains the other. B's and D's intervals are nested, not disjoint, so they cannot be a cross-edge pair.
Why D tempts people
Being an ancestor and descendant only means a tree path connects them; it does not require a direct back edge. A back edge would exist only if some vertex in D's subtree pointed directly back to B, a separate fact you would need to check on the actual edges.

89. Edge Classification

Section

Section 4

90. Step zero: what could an edge possibly be?

Step zero

Discussion prompt

Before the four names: you run DFS and it builds a tree. Every edge of the graph either is in that tree or is not. For the ones that are not, list every structurally different place the other endpoint could sit.

Hint: Above you, below you, or off to the side entirely.

Answer:

Either it points back up to an ancestor, or down to a descendant it did not discover, or across to a vertex in a completely separate part of the tree. Those three plus the tree edges themselves are the only possibilities — which is why there are exactly four categories and not five.

91. Tree edges

Concept

A tree edge is an edge DFS actually uses to discover a new vertex — followed when moving from a gray vertex to a white, undiscovered neighbor.

Tree edges, taken together, form the DFS tree (or forest, for a graph with multiple components) — the skeleton of parent-to-child discoveries the traversal built.

92. Direction changes what is reachable

Picture it

Animation

Shows: Direction changes what is reachable — a rendered Manim animation.

Rendered with Manim.

Takeaway: Following arrows only, some vertices may not be reachable at all.

93. Predict: which edge type proves a cycle exists?

Prediction

Predict first

Only one of the four edge types is direct evidence of a cycle. Which, and why does it prove it?

  • A back edge, because its target is an ancestor still on the current path
  • A cross edge, because it joins two separate branches
  • A forward edge, because it skips ahead in the tree

Correct: A back edge.

Why: A back edge points at a gray vertex, meaning that vertex is an ancestor still open on the current path. The tree path from that ancestor down to where you stand, plus this one edge closing back up to it, is a cycle you can point at explicitly. Cross and forward edges point into finished territory, so no route leads back and no cycle is formed.

94. Back edges — the cycle signal

Concept

A back edge connects a vertex to one of its own ancestors — a vertex still gray, still open on the current path, when the edge is examined.

Finding a back edge means DFS has looped back to somewhere still in progress on the current path. That is only possible if the graph contains a cycle.

\[ \text{back edge } (u,v):\quad d[v] < d[u] < f[u] < f[v],\ v \text{ still gray} \]

95. Family tree analogy for back edges

Intuition

Picture the DFS tree as a family tree, drawn as you meet people while walking down generations. A tree edge introduces a new child. A back edge is like meeting your own grandparent again, further down the line — a loop to someone still 'above' you on the same branch.

Impossible in a real family tree, but very possible in a graph — and whenever it happens, it exposes a cycle running through the ancestor and back down to the current vertex.

96. Which category? Do not trace — just classify.

Discrimination

Sort into buckets

For each description of the edge from u to v, name the category.

Tree edge
v was white when the edge was examined
Back edge
v was gray when the edge was examined
Forward edge
v was black, and v is a descendant of u
Cross edge
v was black, and v is in an unrelated branch
tree
White means undiscovered, so this edge is the one that discovers v. Those edges are exactly the tree.
back
Gray means open and above you, so the edge points at an ancestor. This is the cycle signal.
fwd
Black and below you: v was reached earlier by a longer route through your own subtree, so this edge skips ahead.
cross
Black and unrelated: v belongs to a branch already finished, so the edge crosses between parts of the forest.

97. Forward edges

Concept

A forward edge connects a vertex to one of its own descendants — a vertex already fully finished (black) by the time the edge is examined, but whose interval nests inside the current vertex's still-open interval.

\[ \text{forward edge } (u,v):\quad d[u] < d[v] < f[v] < f[u] \]

A forward edge is a bit like a shortcut: it points from an ancestor directly down to a descendant, skipping past the ones already found through the actual tree edges.

98. Cross edges

Concept

A cross edge connects two vertices where neither is an ancestor of the other — their discovery/finish intervals are completely disjoint, one ending entirely before the other begins.

\[ \text{cross edge } (u,v):\quad f[v] < d[u] \ \text{or}\ f[u] < d[v] \]

Cross edges often point 'sideways,' or backward in time, to an already-finished subtree explored earlier in the traversal — a completely separate branch, or an earlier branch of the same tree.

99. Process: classifying an edge by looking at it

Intuition

Watch me not know the answer. This is what the first two minutes actually look like.

Given a DFS run, classify each edge as tree, back, forward or cross.

Try classifying by direction on the drawing: up the tree is a back edge, down is forward, sideways is cross

Why: The picture makes the categories look obvious, and for the first few edges it works.

The drawing lies as soon as the tree gets deep

Why: Which node looks above which depends on how you laid the picture out, and cross edges between different subtrees are impossible to distinguish from forward edges by eye.

Dead end. Not a mistake — a move that was worth trying and did not pay off. This happens in most proofs.

Back up. Use the numbers, not the picture

Why: Discovery and finish times give a nesting structure: one interval is inside another, or the two are disjoint. That is a fact about the run, independent of any drawing.

\[ d[u] < d[v] < f[v] < f[u] \;\Longrightarrow\; v \text{ is a descendant of } u \]

Replacing a visual judgment with a numeric test is a recurring move in this course. The picture generates the intuition; the numbers carry the proof.

The expert does not see the whole path in advance. The expert tries something, reads the result, and adjusts. That is the skill.

100. Compare the four edge types side by side

Comparison

Comparison matrix

Complete the table. The colour column is the one you can actually check while the algorithm runs.

Edge typeColour of v when seenSignals a cycle?
Treewhiteno
Backgrayyes
Forwardblackno
Crossblackno

101. Classifying edges with discovery/finish intervals

Concept

Every edge DFS examines falls into exactly one of the four categories, decided by comparing intervals at the moment the edge is examined.

Edge typeWhat you see when you examine it
Treev is white (undiscovered) — you recurse into it right now.
Backv is gray — v is a currently open ancestor.
Forwardv is black, and v's interval nests inside u's still-open interval.
Crossv is black, and v's interval is disjoint from u's (finished before u started).

102. Classify these edges from one trace

Sorting

Sort into buckets

DFS on a directed graph produced the tree A to B to C, and separately A to D. Classify each remaining edge.

Tree
B to C
Back
C to A
Forward
A to C
Cross
D to B
tree
This is an edge the traversal actually used to discover a new vertex, so it is part of the tree by definition.
back
C points up at A, which is still open above it on the path. That closes a cycle.
fwd
A points down at C, which sits in A's own subtree but was discovered through B rather than directly.
cross
D points at B, which is finished and lives in a different branch entirely, so there is no ancestor relationship either way.

103. Classify every edge from the DFS trace

Worked example

Using the discovery/finish times from the earlier trace, classify each of the 8 directed edges.

VertexDiscoveryFinish
A112
B27
C811
D36
E45
F910

Classify the tree edges

Why: A to B, A to C, B to D, C to F, and D to E were each followed while the target vertex was still white — exactly the 5 tree edges.

Classify E to B

Why: When E examines this edge, B has interval [2,7] and E's discovery time (4) falls inside it while B is still open — B is an ancestor of E through B to D to E. So E to B is a back edge.

\[ d[B]=2 < d[E]=4 < f[E]=5 < f[B]=7 \]

Classify A to D

Why: When A examines this edge, D already has both a discovery and finish time, and D's whole interval sits inside A's still-open interval. A is an ancestor of D through A to B to D, so this is a forward edge.

\[ d[A]=1 < d[D]=3 < f[D]=6 < f[A]=12 \]

Classify C to D

Why: When C examines this edge, C is open at time 8, but D already finished at time 6 — entirely before C even started. The intervals [3,6] and [8,11] are disjoint, so neither is an ancestor of the other: a cross edge.

\[ f[D]=6 < d[C]=8 \]

Verify all 8 edges are accounted for

Why: Five tree edges (A-B, A-C, B-D, C-F, D-E) plus one back edge (E-B), one forward edge (A-D), and one cross edge (C-D) totals eight — matching every edge in the graph, with none left unclassified.

EdgeClassification
A to BTree
A to CTree
A to DForward
B to DTree
C to DCross
C to FTree
D to ETree
E to BBack

104. DFS plunges, then backtracks

Picture it

Animation

Shows: DFS plunges, then backtracks — a rendered Manim animation.

Rendered with Manim.

Takeaway: Same graph, same start, completely different order.

105. Step each edge and read its colour

Pattern

Step through it

Before each step, predict the colour of the far endpoint. The colour alone decides the classification.

  1. White means undiscovered, so the edge that reaches it becomes a tree edge.
  2. Same again, one level deeper.
  3. Gray is the one that matters: it means an ancestor on the current path, so this edge closes a cycle.
  4. Black in a different branch means cross. Black below you would mean forward.

106. Decision point: back edge means cycle

Intuition

What move should we make next?

The claim to prove, in a directed graph:

\[ \text{DFS finds a back edge} \iff \text{the graph has a cycle} \]

Two directions to prove, and they are not equally easy.

Which direction is nearly free, and which one needs a move? Name the move for the hard direction.

_Look at your toolkit. Say a move number out loud before this slide advances._ A wrong guess is useful. A silent guess is not.

107. Test the claim: no back edges means what?

Hypothesis

Predict first

You run DFS over an entire directed graph and find no back edges anywhere. What have you just proved?

  • The graph is acyclic — it is a DAG
  • The graph is connected
  • The graph is undirected

Correct: The graph has no cycles at all. It is a directed acyclic graph.

Why: Back edges and cycles imply each other. Any cycle forces DFS to eventually look at an edge pointing back to a vertex still gray on the current path, so no back edges means no cycles. This is not merely a test that usually works — it is the standard way cycle detection is actually implemented, and it costs nothing beyond the traversal you were doing anyway.

108. Detect a cycle using a back edge

Worked example

Claim: the graph from the DFS trace contains a directed cycle, and the back edge found during DFS reveals exactly which vertices form it.

Recall the back edge

Why: DFS found that E to B is a back edge — B is still gray, an open ancestor, when E examines this edge.

\[ \text{back edge: } E \to B \]

Trace the tree path from B down to E

Why: The tree edges show B to D, then D to E — so B reaches E by following two tree edges.

\[ B \to D \to E \ \text{(tree edges)} \]

Combine the tree path with the back edge

Why: Following the tree path from B to E, then taking the back edge from E straight to B, traces a closed loop.

\[ B \to D \to E \to B \]

Verify this is genuinely a cycle

Why: Every edge in the loop, B to D, D to E, and E to B, is a real edge in the graph, and the loop starts and ends at the same vertex B. A back edge during DFS on a directed graph always signals a cycle exactly like this one.

\[ B \to D \to E \to B\ \checkmark \]

109. A back edge is a cycle you can point at

Picture it

Animation

Shows: A back edge is a cycle you can point at — a rendered Manim animation.

Rendered with Manim.

Takeaway: A to B to C, then C points back at a still-open A. That is the cycle.

110. Watch the gray chain close on itself

Invariant

Step through it

Track which vertices are gray. At the moment the cycle is found, what exactly do the gray vertices form?

  1. The gray set starts as just the source.
  2. It grows into a path, never a scattered set.
  3. Three gray vertices, still one unbroken chain from root to here.
  4. The chain plus the back edge IS the cycle — you can point at it, not just assert it.

111. Why do undirected graphs lose two categories?

Counterexample

Discussion prompt

In an undirected graph, DFS only ever produces tree edges and back edges. Try to construct a cross edge, and explain what stops you.

Hint: An undirected edge is examined from both ends.

Answer:

You cannot. A cross edge would need v finished and unrelated to u when the edge from u is examined. But an undirected edge is visible from both ends, so while v was being explored it would have seen u — and u was white or gray then, so v would have taken the edge itself and made it a tree or back edge. The edge gets claimed before a cross classification is ever possible.

112. Undirected graphs only show tree and back edges

Concept

In an undirected graph, DFS can only ever produce tree edges and back edges — never forward or cross edges.

The reason: every edge is examined from both ends. If the edge would have been a forward edge from one end, it is discovered as the matching back edge from the other end first, since that end is visited earlier — so it always ends up classified as tree or back.

113. Why this matters — building blocks for later algorithms

Concept

These traversals are not the end goal — they are the engine underneath much of the rest of the course. Cycle detection just checks whether DFS ever finds a back edge.

Ordering tasks with prerequisites, a topological sort, works by listing vertices in decreasing order of DFS finish time. Finding connected components, testing bipartiteness, and computing shortest paths in unweighted graphs all start from exactly the BFS or DFS you just traced by hand.

114. Eliminate: which single check identifies a back edge?

Elimination

Eliminate the wrong options

You are inside the neighbour loop and want to decide, right now, whether this edge is a back edge. Which test works?

  • A. Check whether v is currently gray.
  • B. Check whether the discovery time of v is less than that of u.
  • C. Check whether v has already been visited at all.
  • D. Check whether v is a neighbour of u.

Survives elimination: A

Why: Gray means discovered but not finished, which is exactly the definition of an ancestor still on the current path. It is a single constant-time colour lookup available at the precise moment you need it, which is why cycle detection adds nothing to the running time.

115. How to classify any edge

Pattern

1. Note the color of the target vertex the moment the edge is examined

Why: White means tree edge — you are about to recurse into it right now.

2. If the target is gray, it is a back edge

Why: Gray means the target is a currently open ancestor on the same path — a loop back, revealing a cycle.

3. If the target is black, compare the two discovery/finish intervals

Why: If the target's interval nests inside the source's still-open interval, it is a forward edge. If the two intervals are disjoint, it is a cross edge.

116. The colour decides the classification

Picture it

Animation

Shows: The colour decides the classification — a rendered Manim animation.

Rendered with Manim.

Takeaway: One constant-time colour lookup, available exactly when you need it.

117. Check yourself: classify the edge

Check

Vertex C has discovery/finish times (8, 11) and vertex D has discovery/finish times (3, 6). DFS examines the edge C to D.

Check your understanding

How should the edge C to D be classified?

  • A. Cross edge — D's interval [3,6] is entirely disjoint from and precedes C's interval [8,11]. (correct)
  • B. Forward edge — D is a descendant of C that finished early.
  • C. Back edge — D is an ancestor of C still open on the current path.
  • D. Tree edge — D was undiscovered when C examined it.

Answer: A

Why: D's finish time (6) is less than C's discovery time (8), so D's whole interval ends before C's interval even begins. The intervals are disjoint, meaning neither vertex is an ancestor of the other — exactly the definition of a cross edge.

Why B tempts people
A forward edge requires D's interval to be nested inside C's still-open interval, with C's discovery before D's and C's finish after D's. Here D's interval ends at 6, before C's even starts at 8 — that is disjoint, not nested.
Why C tempts people
A back edge requires the target to still be gray, open, when examined — but D finished at time 6, long before C started at time 8. D is black, not gray, when C examines this edge.
Why D tempts people
A tree edge requires the target to be white, undiscovered, at the moment it is examined. D was already discovered and fully finished by the time C looks at this edge, so it cannot be a tree edge.

118. Running Time

Section

Section 5

119. Recall: what does the V plus E bound actually count?

Warm-up

Discussion prompt

Before the derivation: the traversal bound is written as V plus E rather than as a single letter. Why two terms instead of one?

Answer:

Because two independent things are being counted, and neither controls the other. You touch each vertex once for its own bookkeeping, and you look at each edge once while scanning neighbour lists. A graph can be huge in vertices and tiny in edges or the reverse, so neither term can be dropped.

120. Big-O and Theta notation refresher

Concept

To describe running time, we describe how the number of basic steps grows as the input grows, ignoring constant factors and lower-order details.

\[ O(\cdot)\ \text{gives an upper bound}; \quad \Theta(\cdot)\ \text{gives a tight bound (upper and lower)} \]

For BFS and DFS, the input size has two parts: the number of vertices and the number of edges. We will show the running time is proportional to their sum.

\[ \Theta(|V| + |E|) \]

121. Estimate: how many neighbour inspections in total?

Estimation

Predict first

An undirected graph has 10 vertices and 15 edges. Across the whole traversal, how many times in total is a neighbour-list entry examined?

  • 30
  • 15
  • 150

Correct: 30 — each undirected edge appears in two lists, so twice the edge count.

Why: An undirected edge is stored in both endpoints' lists, so summing the degrees over all vertices gives exactly twice the number of edges. That sum is the total number of neighbour inspections, and it is why the bound is linear in the edge count rather than in the square of the vertex count.

122. Visiting neighbors with an adjacency list

Concept

Both BFS and DFS, when a vertex is processed, look at exactly its own adjacency list — nothing more. Checking a vertex's neighbors costs time proportional to that vertex's degree, not to the total vertex count.

That is the entire reason these traversals are fast: no vertex ever scans the whole graph looking for its neighbors. It only reads the list it already has.

123. The adjacency list stores only real edges

Picture it

Animation

Shows: The adjacency list stores only real edges — a rendered Manim animation.

Rendered with Manim.

Takeaway: Reading every list once costs 2E, not V squared.

124. Why the work is proportional to vertices plus edges

Intuition

Every vertex is processed exactly once — dequeued once in BFS, or discovered-and-finished once in DFS. That is where the vertex term comes from.

Every edge is examined a small, constant number of times total across the whole run — once from each endpoint that processes it. That is where the edge term comes from. Add those two costs together and the total work is proportional to vertices plus edges, never to vertices squared.

125. Why nested loops are not quadratic here

Picture it

Animation

Shows: Why nested loops are not quadratic here — a rendered Manim animation.

Rendered with Manim.

Takeaway: Ask what the inner work TOTALS, not how deep the nesting goes.

126. Scale it up: when does the matrix actually hurt?

Scale up

Parameter explorer

Drag V and watch how a matrix-based traversal grows against a list-based one on a sparse graph. Where do they visibly part company?

\[ \text{matrix work} \approx {V}^2 \]

  • V — from 10 to 2000: number of vertices

127. Summing degrees over all vertices

Concept

Adding up the length of every vertex's adjacency list counts each edge exactly twice in an undirected graph — once from each endpoint.

\[ \sum_{v \in V} \deg(v) = 2|E| \quad (\text{undirected}) \]

So the total number of neighbor-checks across the entire traversal is exactly twice the edge count for an undirected graph — a fixed multiple of the edge count, not something that grows with the number of vertices on its own.

128. Push the extremes: what if the graph has no edges?

Edge cases

Discussion prompt

Two extremes. First, a graph with V vertices and no edges at all. Second, a complete graph where every vertex joins every other. What does the V plus E bound become in each, and what does that tell you about which term matters?

Hint: How many edges does a complete graph have?

Answer:

With no edges the bound is just V — you still touch every vertex, which is why the V term cannot be dropped. In a complete graph the edge count is about V squared over two, so the bound becomes quadratic and the E term completely dominates. The same linear formula covers both because it is linear in the size of the input, not in the number of vertices.

129. Tally the operations in the BFS trace

Worked example

Count the actual operations performed in the earlier 6-vertex BFS trace and compare the total to vertices-squared.

VertexDegree (adjacency list length)
A2
B2
C3
D3
E2
F2

Count the dequeue operations

Why: Each of the 6 vertices is dequeued exactly once over the whole trace.

\[ 6 \text{ dequeues} \]

Count the neighbor-checks

Why: Summing the degrees: 2+2+3+3+2+2 gives 14 neighbor-checks total, matching twice the edge count.

\[ 2+2+3+3+2+2 = 14 = 2|E| \]

Verify the total is proportional to vertices plus edges, not vertices squared

Why: Total operations: 6 dequeues plus 14 neighbor-checks equals 20 — close to vertices plus edges (6+7=13), within a small constant factor. Compare to vertices squared: 6 squared is 36, well above the actual work, and the gap only grows on bigger, sparser graphs.

\[ 6 + 14 = 20 \ \text{vs.} \ |V|^2 = 36\ \checkmark \]

130. Watch the tally split into V plus E

Scale up

Step through it

Two counters run at once. Predict which one grows faster on a graph with many edges.

  1. The vertex counter goes up once per dequeue — at most V times in total.
  2. The edge counter goes up by the degree, which is why it moves in jumps.
  3. Neither counter bounds the other; that is why the answer has two terms.
  4. V from touching each vertex, E from reading each edge. Never V times E.

131. The same bound applies to DFS

Concept

DFS follows the identical accounting: every vertex is discovered and finished exactly once, and every edge is examined exactly once from the vertex that processes it.

So DFS's running time is also proportional to vertices plus edges — the same bound as BFS, for the same underlying reason: with adjacency lists, no vertex ever does more work than looking at its own neighbors.

\[ T_{\text{DFS}} = \Theta(|V| + |E|) \]

132. Something is wrong: two nested loops, but not quadratic

Anomaly

Predict first

Traversal has a loop over vertices with a loop over neighbours inside it. Nested loops usually multiply. Why is this one not V squared?

  • The inner loop's length varies, and its lengths across all vertices sum to E rather than multiplying
  • The inner loop runs a constant number of times
  • It is quadratic; the linear bound is only an average case

Correct: The inner loop lengths add up to E instead of multiplying by V.

Why: Multiplying only applies when the inner loop runs the same number of times regardless of the outer variable. Here it runs degree-of-u times, and those degrees sum to twice the edge count over the whole run. The right question about a nested loop is never how deep it nests, but what the inner work totals across all outer iterations.

133. Trap: assuming traversal time is quadratic in the vertices

Trap

The trap

A student reasons: for each vertex, we might have to check it against every other vertex to see if it is a neighbor, so the running time must be proportional to vertices squared.

\[ \text{Assumed: } T = \Theta(|V|^2) \]

Ignore the adjacency-list representation

Why: This reasoning assumes checking whether one vertex is a neighbor of another requires scanning all possible vertices for every vertex — that is how an adjacency MATRIX works, not an adjacency list.

The fix

With adjacency lists, each vertex only looks at its own neighbors — never at every other vertex.

\[ \text{Actual: } T = \Theta(|V| + |E|) \]

Count only real neighbor-checks

Why: A vertex's processing time is proportional to its own degree, not to the total vertex count. Summed over all vertices, that totals twice the edge count.

Confirm with the earlier tally

Why: On the 6-vertex, 7-edge graph, the real operation count was 20 — nowhere near vertices squared (36) — and on a sparse graph with many more vertices than edges, the gap between vertices-plus-edges and vertices-squared grows enormous.

\[ 20 \ll 36 \]

134. Reasoning about traversal running time

Pattern

1. Confirm the graph is stored as an adjacency list

Why: The vertices-plus-edges bound depends on this representation; an adjacency matrix changes the accounting entirely.

2. Count one unit of work per vertex

Why: Each vertex is dequeued once in BFS, or discovered-and-finished once in DFS — this contributes a term proportional to the number of vertices.

3. Count one unit of work per edge-endpoint examined

Why: Summing every vertex's degree gives at most twice the edge count — this contributes a term proportional to the number of edges.

4. Add the two terms — never multiply them

Why: The total is vertices plus edges, added together, never vertices times edges and never vertices squared. Multiplying or squaring only happens if you mistakenly re-scan the whole vertex set for every vertex.

135. Check yourself: naming the running time

Check

A graph is stored as an adjacency list and has 500 vertices and 1,200 edges.

Check your understanding

Which best describes the running time of BFS or DFS on this graph?

  • A. Proportional to the number of vertices plus the number of edges. (correct)
  • B. Proportional to the square of the number of vertices.
  • C. Proportional to the square of the number of edges.
  • D. Proportional to the number of vertices multiplied by the number of edges.

Answer: A

Why: With an adjacency list, each vertex is processed once and each edge is examined a constant number of times total, so the total work adds the vertex term and the edge term together: proportional to vertices plus edges.

Why B tempts people
This treats every vertex as if it must be compared against every other vertex, which is how an adjacency matrix behaves. With an adjacency list, a vertex only looks at its actual neighbors, not the full vertex set.
Why C tempts people
There is no step in BFS or DFS that squares the edge count; each edge is examined a small constant number of times total across the whole traversal, never against every other edge.
Why D tempts people
Multiplying vertices by edges would only occur if every vertex had to re-examine every edge in the graph. Adjacency lists let each vertex look only at its own edges, so the costs add rather than multiply.

136. Connect it up: one traversal, many algorithms

Connect it up

Draw it

Draw the family tree. Put BFS and DFS at the top, and underneath each write the algorithms and problems that are really just that traversal with something added.

137. Toolkit update

Concept

Moves added today: none.

That is a result, not a gap. Everything in this lesson was proved with moves you already owned.

Moves you reused today:

The BFS correctness proof is induction on distance, and the running-time argument is a loop invariant plus dropping constants. No new machinery — new subject matter, old tools.

Full toolkit so far: #1 through #14.

Next session opens with you naming every one of these from memory, before any new material.

138. Past some size, constants stop mattering

Picture it

Animation

Shows: Past some size, constants stop mattering — a rendered Manim animation.

Rendered with Manim.

Takeaway: Traversal sits on the linear rung, not the quadratic one.

139. Exit ticket: what would you least want tomorrow?

Exit ticket

Predict first

Be honest about the weakest spot while it is still fresh.

  • Tracing BFS and reading distances off it
  • Tracing DFS and reading discovery and finish times
  • Classifying an edge as tree, back, forward or cross
  • Explaining why the running time is V plus E and not quadratic

Correct: Whichever you picked is where tonight's practice should start.

Why: There is no wrong answer here. Naming the weak spot out loud is what converts a vague sense of unease into a specific problem you can actually go and practise, and doing it now while the trace is still in memory costs almost nothing.

140. What you can do now

Recap

You can now trace and reason about both fundamental graph traversals.

TraversalStructureGuarantee
BFSqueueShortest path by edge count, unweighted graphs only
DFSstack / recursionDiscovery/finish times; reveals cycles via back edges

Sources

  1. Cormen, Leiserson, Rivest, Stein, Introduction to Algorithms, 3rd/4th ed., Ch. 22 (Elementary Graph Algorithms: BFS, DFS, edge classification, running time) — MIT Press.
  2. Northeastern University CS 3000 (Algorithms and Data) course materials on graph traversal — Course reference; treatment consistent with the standard CLRS presentation.
  3. All traces (BFS queue/visited-set states, DFS discovery/finish times, edge classifications, and operation tallies) recomputed and hand-verified against the exact adjacency lists given in this deck. — Verified 2026-07-18.
  4. Northeastern University CS 3000, Algorithms and Data (Summer 2026) — course page and syllabus — course.ccs.neu.edu/cs3000su26. Sets Cormen, Leiserson, Rivest and Stein, Introduction to Algorithms (3rd ed.) as the textbook; listings follow its conventions.
  5. CS 3000 course notes and midterm references circulated by students — github.com/vigneshsaravanakumar404/CS-3000-Algorithms-Data. Notes are typeset with the algpseudocode package, which is the style the listings in this deck follow.

Want this taught 1-on-1? Alexander tutors CS3000 Algorithms — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108