Exercises — DFS applications — cycle detection (directed and undirected)
This page is a self-test ladder. Work each problem on paper before opening its solution. Levels climb from "just spot it" to "design the whole thing".
If any word here is unfamiliar (White/Gray/Black, back edge, parent-skip), reread the parent first: the topic note, and lean on Depth-First Search for the traversal engine.
Level 1 — Recognition
Goal: given a tiny graph, say cycle / no cycle and name the edge that proves it.
L1.1
Directed graph with edges , , . Cycle or not? Which colour does every node end in?
Recall Solution
No cycle. It is a straight path — nobody points back. DFS from : paint Gray one after another, hit no Gray neighbour, then finish them back up so all four end Black. A finished (Black) chain with no back edge = acyclic.
L1.2
Undirected graph with edges , , . Cycle or not? Which single edge closes it?
Recall Solution
Cycle. It is a triangle. Walking , from we see neighbour which is visited and not the parent (). The edge closes the loop.
L1.3
Directed self-loop: one node with edge . Cycle?
Recall Solution
Yes — the smallest possible directed cycle. Enter , paint it Gray, scan its neighbours, find itself which is currently Gray → back edge → cycle immediately.
Level 2 — Application
Goal: run the algorithm by hand and produce the correct verdict.
L2.1
Directed: , , , . Run 3-colour DFS from . Cycle?
Recall Solution
dfs(A): A=Gray. Try .dfs(B): B=Gray. Try .dfs(C): C=Gray. Neighbour is Gray → back edge → cycle = True.
The 2-cycle is the culprit.
L2.2
Undirected: , , , . Run dfs(A,-1). Cycle? Name the closing edge.
Recall Solution
dfs(A,-1): visit A → go .dfs(B,A): visit B → skip parent → go .dfs(C,B): visit C → skip parent → go .dfs(D,C): visit D → parent is (skip). Neighbour is visited and parent → cycle = True.
Closing edge: . The loop is .
L2.3
Undirected star: centre joined to , , , (four edges, nothing else). Cycle?
Recall Solution
No cycle. From we visit each leaf; each leaf's only neighbour is = its parent = skipped. A star on nodes has edges → it is a tree → acyclic.
Level 3 — Analysis
Goal: reason about why a rule holds, or debug a subtle case.
L3.1
A student uses the undirected parent-trick on the directed graph , . Their code returns "no cycle". Trace their mistaken run and give the correct verdict.
Recall Solution
Their buggy dfs(u,parent) ignores any visited neighbour equal to parent:
dfs(A,-1): visit A → go .dfs(B,A): visit B → neighbour is visited and equals parent → skipped → returns no cycle.
Wrong. The true graph has the 2-cycle . The parent-skip erased exactly the edge that proves it. Correct verdict: cycle exists. Directed edges are not symmetric, so there is no "spurious reverse copy" to forgive — the edge is a genuine distinct arrow. Use 3 colours instead; sees still Gray → back edge.
L3.2
In the multigraph below (undirected), there are two edges between and and nothing else. Does the plain v != parent algorithm detect the cycle? Should it?
Recall Solution
Two parallel edges are a cycle (go out on edge 1, come back on edge 2). But plain parent-skip fails:
dfs(A,-1): visit A → go via edge 1.dfs(B,A): visit B → scan neighbours: (via edge 2) is visited but equals parent → skipped. Missed!
Fix: track the edge index you arrived on, not just the parent vertex. Then edge 2 ( arrival edge) is recognised as a genuine return route → cycle. (See figure below.)

L3.3
Prove: an undirected simple graph with nodes and edges must contain at least one cycle.
Recall Solution
A forest (acyclic undirected graph) with nodes and connected pieces has exactly edges. Since , a forest has at most edges. We have edges , so the graph is not a forest → it contains a cycle. (Equivalently: run DFS; with edges but only tree edges possible, at least one edge is a back edge.)
Level 4 — Synthesis
Goal: combine cycle detection with a neighbouring idea.
L4.1
You must run Topological Sort on a directed graph. Explain precisely how the directed cycle test decides whether a topological order even exists, and what edge type blocks it.
Recall Solution
A topological order exists iff the graph is a DAG (no directed cycle). DFS-based topo sort emits nodes in decreasing finish time. If during that DFS you ever find an edge to a Gray node — a back edge — a directed cycle exists, so no valid ordering exists and topo sort must fail/report the cycle. So the same Gray-check is the acyclicity gatekeeper. See Back Edges and Edge Classification for why only back edges (not forward/cross) signal cycles.
L4.2
Give a graph where DFS-based undirected cycle detection and Union-Find (DSU)-based detection would flag the same cycle, then explain the different "moment of discovery".
Recall Solution
Triangle , , .
- DFS: discovers the cycle when, deep in recursion at , it sees visited non-parent (edge ).
- DSU: processes edges one at a time. It unions , then (via ). When it reaches , both endpoints are already in the same set → cycle. Same closing edge , but DSU never traverses depth — it just asks "are these two already connected?" DSU shines when you only have an edge list and no need for traversal order.
L4.3
A single DFS colours a directed graph and finds zero Gray-hits. Someone claims "then the graph is also bipartite." True or false? Justify using the right tool.
Recall Solution
False. No directed cycle just means the graph is a DAG; it says nothing about 2-colourability. Bipartiteness is about odd-length undirected cycles and is tested with Bipartite Checking (2-colour DFS/BFS on the undirected view). A DAG like , , has an odd undirected cycle-free structure here, but in general acyclic-directed and bipartite are unrelated properties. Different question → different tool.
Level 5 — Mastery
Goal: design, prove correctness, and handle every case.
L5.1
Design an algorithm that, in one DFS pass, returns not just whether a directed cycle exists but prints the actual cycle vertices in order. State the invariant and complexity.
Recall Solution
Keep color[] and a parent[] (who called us). Run the standard 3-colour DFS. The moment dfs(u) finds a neighbour v that is Gray, you have the back edge . Reconstruct the cycle by walking parent[] from u back up until you reach v, then append v:
cycle = [u]
x = u
while x != v:
x = parent[x]
cycle.append(x)
reverse(cycle) # now v ... u, plus the edge u->v closes it
Invariant: at any instant the set of Gray nodes forms exactly the current recursion path (a simple chain via parent[]), so walking parent[] from u up to the Gray ancestor v traces a real path, and the back edge closes it into a genuine cycle. Complexity: still for the DFS; the reconstruction walk is , absorbed into the total.
L5.2
Write the complete driver that detects a directed cycle in a possibly disconnected graph with self-loops, and prove it never misses and never false-alarms.
Recall Solution
for u in all_vertices:
if color[u] == WHITE:
if dfs(u): return True # dfs is the 3-colour version
return False
Never misses: every White vertex eventually starts a DFS (the driver loop), and every edge out of a Gray vertex is scanned, so any back edge — including a self-loop caught while is Gray — is seen. Never false-alarms: we only return True on a Gray neighbour; a Gray neighbour is provably still on the stack (an ancestor), so a real directed path exists from it down to the current node, and the edge back to it closes a real cycle. Black neighbours (finished) are skipped, correctly ignoring forward/cross edges. Complexity: time, space.
L5.3
A graph has vertices. It is undirected, connected, and acyclic. How many edges does it have? Now add one edge — exactly how many simple cycles are created?
Recall Solution
Connected + acyclic = a tree, so it has edges. Adding one edge between two already-connected tree nodes creates exactly one simple cycle: the unique tree path between the endpoints, plus the new edge. So: 5 edges, and adding one gives 1 new simple cycle.
Recall Feynman recap: the one sentence that grades L1–L5
"In directed graphs a cycle is a live back edge (neighbour still Gray); in undirected graphs a cycle is a visited non-parent neighbour (track the edge if the graph is a multigraph) — and always loop over every component."
Connections
- Depth-First Search — the engine every solution above runs on.
- Topological Sort — L4.1: acyclicity is exactly the precondition.
- Union-Find (DSU) — L4.2: edge-list alternative for undirected cycles.
- Back Edges and Edge Classification — why only back edges signal cycles.
- Strongly Connected Components — finish-time DFS, same machinery.
- Bipartite Checking — L4.3: a different DFS signal.