Exercises — DFS — algorithm, stack - recursion, O(V+E), visited array
Before we start, one shared convention so no symbol appears unexplained:
Level 1 — Recognition
(Can you read a graph and produce the DFS order by hand?)
Recall Solution L1.1
Walk the rule "mark on arrival, then descend into the first unvisited neighbor."
- Arrive
0, mark it. Order so far:0. First neighbor is1, unvisited → go in. - Arrive
1, mark. Order:0,1.1's only neighbor is3, unvisited → go in. - Arrive
3, mark. Order:0,1,3.3has no neighbors → dead end, backtrack to1. 1has no more neighbors → backtrack to0.0's next neighbor is2, unvisited → go in. Arrive2, mark. Order:0,1,3,2. DFS order =0, 1, 3, 2. Notice it is not0,1,2,3— that "finish a level before going deeper" pattern is BFS, not DFS.
Recall Solution L1.2
- Arrive
0, mark →0. First neighbor1, unvisited → in. Arrive1, dead end, back to0. - Next neighbor
2→ in, dead end, back to0. - Next neighbor
3→ in, dead end. DFS order =0, 1, 2, 3. With no deeper structure, DFS just walks the neighbor list of the root.
Level 2 — Application
(Apply the visited rule and the complexity formula to concrete numbers.)
Recall Solution L2.1

0→1, 1→2) are the edges DFS descends, and the coral dashed arrow (2→0) is the edge DFS tries but rejects because 0 is already marked. Read the steps against it:
- Arrive
0, mark →0. Neighbor1unvisited → in (follow the first grey arrow). - Arrive
1, mark →0,1. Neighbor2unvisited → in (second grey arrow). - Arrive
2, mark →0,1,2.2's neighbor is0, butvisited[0]is True → skip (the coral dashed arrow in the figure). This is the exact line where the visited array stops an infinite loop. - Backtrack all the way out.
DFS order =
0, 1, 2. Work: vertices marked once, directed edges scanned once → basic operations.
Recall Solution L2.2
In every distinct pair of vertices is joined by exactly one edge, so counting edges = counting pairs. To count pairs: vertex 0 pairs with the other , vertex 1 with , and so on — that's ordered picks, but pair {0,1} and {1,0} are the same edge, so we divide by :
(This " choose " count is written — the symbol just means "the number of ways to pick an unordered pair out of things," which is exactly the number of possible edges. We derived it above rather than quoting it.)
DFS cost basic operations (each treated as one unit). In big-O this is ; here numerically 15.
Recall Solution L2.3
The Handshake Lemma says every undirected edge adds to two endpoints' degrees, so . 6 edges. Now the raw operation count. Two separate piles of work:
- Vertex-marks: each of the vertices is marked once → operations.
- Edge-inspections: DFS walks each undirected edge from both endpoints, so it does inspections (not ). Raw total operations. In asymptotic notation this is still , because the factor of on the edge term is a constant the big-O bound absorbs — but the honest raw count is 18, not . (Writing "" conflates the asymptotic with the raw ; they differ by that constant factor.)
Level 3 — Analysis
(Trace subtle behaviour and explain why the algorithm does what it does.)
Recall Solution L3.1

0 at the top branches to 1 (left) and 2 (right), and both 1 and 2 (the coral arrows) point down to the shared bottom vertex 3. That shared target is exactly why 3 can be reached twice. Keep an eye on it while tracing the stack.
Recall the iterative loop: pop a node; if already visited, continue; else mark, process, then push each unvisited neighbor. A stack is LIFO — the last pushed is the first popped. The side panel in the figure lists the resulting pop order.
- Stack
[0]. Pop0, mark, process → order0. Push its neighbors1, then2. Stack[1,2]. - Pop
2(top of stack!), mark, process →0,2. Push its unvisited neighbor3. Stack[1,3]. - Pop
3, mark, process →0,2,3. No unvisited neighbors. Stack[1]. - Pop
1, mark, process →0,2,3,1. Its neighbor3is already visited → not pushed. Stack[]. Processing order =0, 2, 3, 1.
Why 3 could have been pushed twice: both 1 and 2 list 3 as a neighbor (the two coral arrows). If 1 had been popped before 3 was visited, we'd push 3 a second time — and the stack would briefly hold two copies of 3. The if visited[u]: continue check on pop is what discards the stale copy. (In this exact ordering 3 was already visited before 1 was popped, so the second push never happened — but the check must still be there for graphs where it does.)
Recall Solution L3.2
DFS does two separate, additive chunks of work:
- Mark each vertex once → units.
- Scan each edge a constant number of times total → units. These add, they don't multiply, because scanning one vertex's neighbors doesn't re-cost the other vertices — the shared visited array guarantees each vertex/edge is touched a bounded number of times overall. Hence . A multiplicative would wrongly claim you redo all edge work for every vertex.
For , :
- — you still must visit and mark all 1000 vertices. Honest.
- — claims zero work, which is nonsense; you obviously still touch every isolated node. This is exactly why both terms are kept: the term protects the isolated-vertex case.
Level 4 — Synthesis
(Combine DFS with another idea to build a small algorithm.)
Recall Solution L4.1

{0,1,2} joined by solid lines, {3,4} joined by one line, and the two lone dots 5 and 6 sitting alone. Each separate cluster is exactly one connected component — the figure is the answer, and the loop below just discovers these clusters one launch at a time.
Loop u from 0 to 6; each time u is unvisited, that's a brand-new component — launch DFS and increment the count.
u=0unvisited → launch. DFS reaches0,1,2. Component #1 = {0,1,2}.u=1,2now visited → skip.u=3unvisited → launch. DFS reaches3,4. Component #2 = {3,4}.u=4visited → skip.u=5unvisited → launch. DFS reaches just5. Component #3 = {5}.u=6unvisited → launch. DFS reaches just6. Component #4 = {6}. Answer: 4 connected components. Cost: the shared visited array spans all launches, so across every launch combined each vertex is marked once ( total) and each edge scanned a bounded number of times ( total). Total = , unchanged — the outer loop does not multiply the cost.
Recall Solution L4.2
Keep two states beyond plain visited: mark a node on-stack (grey) while it is an active ancestor in the current recursion, and done (black) after we finish it. A cycle exists exactly when DFS finds an edge pointing to a node that is currently on-stack.
explore(0): mark0on-stack. Edge0→1→ recurse.explore(1): mark1on-stack. Edge1→2→ recurse.explore(2): mark2on-stack. Edge2→0→0is on-stack → cycle detected! The revealing edge is2→0(a "back edge" to an active ancestor). This is why plainvisited(which the parent used to prevent looping) is not enough for cycle detection — you must distinguish "seen before" from "seen and still on the active path."
Level 5 — Mastery
(Full open-ended reasoning: prove/derive, handle every case.)
Recall Solution L5.1
Post-order = record a vertex when we finish it (after all its descendants are done). Reverse that list. Trace (neighbors in listed order, mark on arrival):
explore(0): go to1.explore(1): go to3.explore(3): no neighbors → finish 3. Post:[3].- Back in
1: done → finish 1. Post:[3,1]. - Back in
0: next neighbor2.explore(2): neighbor3already visited → skip → finish 2. Post:[3,1,2]. - Back in
0: done → finish 0. Post:[3,1,2,0]. Reverse post-order =0, 2, 1, 3. Check every edge points left-to-right in this order:0→1✓,0→2✓,1→3✓,2→3✓. Valid topological order. Why reversal works (for any DAG): when DFS finishes a vertexu, every vertex reachable fromuis already finished (they were explored and popped beforeu). So in post-order,uappears after all its descendants. Reversing putsubefore all its descendants — which is exactly the topological requirement "each vertex before everything it points to." A DAG has no cycle, so no back edge ever forces a contradiction.
Recall Solution L5.2
Vertex work. The visited array blocks re-entry, so explore runs at most once per vertex. Marking + processing is each → summing over the (at most ) explored vertices gives .
Edge work. Inside the single explore(u) call, we scan u's adjacency list once: inspections. Summed over all vertices that get explored:
The reason this sum equals : every directed edge leaves exactly one tail vertex u, so it is counted exactly once inside that tail's explore — no edge is double-counted, none is missed. Hence edge work is .
Total . These add (not multiply) because the two sums range independently — the vertex sum runs over dots, the edge sum over lines — and the visited array bounds each contribution to a constant number of touches.
Degenerate cases:
- (all vertices isolated): the edge sum is , but we still mark each reachable vertex → cost , correctly captured by the term (an claim would wrongly say "no work").
- Single vertex
0:[0](a self-loop): , .explore(0)marks0, scans its one edge0→0, seesvisited[0]=True, and skips — no recursion. Work . Crucially, no infinite loop, because the self-edge hits the visited guard the instant it is followed. This completes the proof for all three cases (general, edgeless, self-loop).
Connections
- DFS — parent note — the algorithm, complexity derivation, and code these exercises drill.
- BFS — Breadth-First Search — contrast the level-order (queue) sibling with DFS's deep-first order.
- Stack data structure · Recursion and the Call Stack — the machinery behind L3.1.
- Connected Components — L4.1. · Cycle Detection — L4.2. · Topological Sort — L5.1.
- Handshake Lemma — L2.3, why .
- 🇮🇳 Hinglish version