← Exam 2 home

1 · DFS, edge types, topological sort, SCCs

DPV Ch 31 exam problem Exam 1 Q7 was DPV 3.3 word for word. The likeliest Ch 3 problem is another DPV 3.x hand trace (3.1, 3.2, 3.4) with the "alphabetically first" rule, or a short design question from HW3.

On the exam: what the question says → what you do
Question saysDo this
"Perform DFS … pick the alphabetically first … give pre and post numbers"Run the stack-of-calls method below. Clock starts at 1 and ticks on every pre and every post.
"Classify each edge"Undirected: tree or back. Directed: tree / forward / back / cross from the pre/post intervals.
"Does it have a cycle?" / "Is it a DAG?"Cycle exists iff DFS found a back edge.
"What topological order is found?"List vertices by decreasing post.
"Sources and sinks?"Source: no incoming edges. Sink: no outgoing edges. Read them off the graph.
"How many topological orderings?"Find the forced chain, multiply the number of ways to order each free group (Exam 1: \(2\times2\times2 = 8\)).
"Run the SCC algorithm"DFS on \(G^R\) (alphabetical), list decreasing post, then explore \(G\) in that order. Each new explore = one SCC.
"Minimum edges to make it strongly connected"\(\max(\#\text{source SCCs}, \#\text{sink SCCs})\) if more than one SCC, else 0.

1. DFS with a clock

DPV's procedure. explore(v) visits everything reachable from \(v\). dfs calls explore from every vertex not yet visited, so it covers the whole graph.

procedure explore(G, v)
  visited(v) = true
  previsit(v)                  // pre(v) = clock; clock = clock + 1
  for each edge (v, u) in E:
    if not visited(u): explore(G, u)
  postvisit(v)                 // post(v) = clock; clock = clock + 1

procedure dfs(G)
  for all v in V: visited(v) = false
  clock = 1
  for all v in V:              // in alphabetical order on the exam
    if not visited(v): explore(G, v)

2. Tracing DFS by hand (alphabetical tie-break)

Do it with a call stack, not by staring at the picture. This is where points are lost.

  1. Write the adjacency list of every vertex with neighbours sorted alphabetically. For a directed graph list only out-neighbours.
  2. Start at the alphabetically first vertex. Push it, write its pre = current clock, add 1.
  3. Look at the top of the stack. Scan its list from where you left off. The first unvisited neighbour: push it, give it a pre. Every visited neighbour you pass: note the edge for classification later.
  4. If the top vertex has no unvisited neighbours left: pop it, write its post = clock, add 1.
  5. When the stack empties, the outer loop picks the alphabetically first unvisited vertex and you continue. The clock does not reset.

Worked: DPV 3.3 (Exam 1 Q7)

ABCDEFGH
DPV 3.3 (Exam 1 Q7).

Adjacency lists: A: C · B: C · C: D, E · D: F · E: F · F: G, H · G: none · H: none.

clockeventstack afterwhy
1pre AAA is alphabetically first
2pre CA CA's only neighbour
3pre DA C DC's list is D, E: take D
4pre FA C D FD's only neighbour
5pre GA C D F GF's list is G, H: take G
6post GA C D FG has no out-edges
7pre HA C D F HF's next neighbour
8post HA C D FH has no out-edges
9post FA C DF's list done
10post DA CD's list done
11pre EA C EC's next neighbour
12post EA CE → F: F already visited (cross edge)
13post CA
14post A(empty)
15pre BBouter loop: B is the first unvisited vertex
16post B(empty)B → C: C already finished (cross edge)
ABCDEFGH1/1415/162/133/1011/124/95/67/8
DPV 3.3 after DFS. Labels are pre/post; thick edges are tree edges, dashed edges are non-tree.
ABCDEFGH
pre/post1/1415/162/133/1011/124/95/67/8

3. Edge types

Undirected graphs: only tree and back edges

A tree edge is one that explore used to discover a new vertex. Every other edge is a back edge (to an ancestor). There are no cross edges: if \(\{u,v\}\) is an edge and \(u\) is explored first, \(u\) must reach \(v\) before \(u\) finishes, so \(v\) becomes a descendant of \(u\). Each undirected edge is classified once (the first time it is looked at).

Directed graphs: tree, forward, back, cross

Edge \(u \to v\)MeaningInterval rule
Treeexplore(u) called explore(v)\(\text{pre}(u) < \text{pre}(v) < \text{post}(v) < \text{post}(u)\)   \([_u\,[_v\,]_v\,]_u\)
Forward\(u\) to a non-child descendant
Back\(u\) to an ancestor (still on the stack)\(\text{pre}(v) < \text{pre}(u) < \text{post}(u) < \text{post}(v)\)   \([_v\,[_u\,]_u\,]_v\)
Crossto an already finished vertex that is neither ancestor nor descendant\(\text{post}(v) < \text{pre}(u)\)   \([_v\,]_v\,[_u\,]_u\)

4. Cycles, DAGs, topological order

Facts to quote.

Counting topological orders

Break the DAG into groups that must come in a fixed sequence, count the orderings inside each group that respect its own edges, multiply. If the groups are not that clean, count by cases: choose which source goes first, delete it, recurse. Exam 1 Q7 (DPV 3.3): \(\{A,B\}\to C\to\{D,E\}\to F\to\{G,H\}\) gives 8.

5. Connected components (undirected)

Each call of explore from the outer loop of dfs finds one connected component. Keep a counter cc, increment it before each such call, and set ccnum(v) = cc in previsit. Linear time. The number of outer-loop calls is the number of components (DPV 3.1 below has 2).

6. Strongly connected components

\(u\) and \(v\) are strongly connected if there is a path \(u\leadsto v\) and a path \(v\leadsto u\). An SCC is a maximal set of mutually connected vertices. Shrinking each SCC to one node gives the metagraph, which is always a DAG (a cycle among SCCs would merge them into one SCC). Every directed graph is a DAG of its SCCs.

SCC(G):
  run dfs on G^R, recording post numbers     (alphabetical tie-break on G^R)
  for v in V in decreasing post order (from G^R):
    if not visited(v):                       (fresh visited marks)
      cc = cc + 1; explore(G, v)             (everything newly reached = one SCC)
  total time O(|V| + |E|)

Why it works (the proof the prof gives)

  1. explore from a vertex in a sink SCC visits exactly that SCC, because nothing leads out of it.
  2. If there is an edge from SCC \(C\) to SCC \(C'\), the largest post in \(C\) is bigger than the largest post in \(C'\). So the vertex with the highest post lies in a source SCC.
  3. The lowest post is not guaranteed to be in a sink SCC. So we can find a source SCC but we need a sink.
  4. \(G^R\) has the same SCCs as \(G\), and its source SCCs are the sink SCCs of \(G\). So the highest post in a DFS of \(G^R\) is in a sink SCC of \(G\). Explore it in \(G\), remove it, and the next highest remaining post is again in a sink SCC of what's left.

Consequence: SCCs come out sink first, i.e. in reverse topological order of the metagraph.

Source/sink SCCs and making a graph strongly connected

7. BFS vs DFS

BFSDFS
ContainerFIFO queuestack (recursion)
Exploreslayer by layer (distance 0, 1, 2, …)as deep as possible, then backs up
Gives youshortest paths in edge count (unit lengths), BFS treepre/post numbers, edge types, cycles, topological order, SCCs
Time\(O(|V|+|E|)\)\(O(|V|+|E|)\)
Trapdoes not give pre/post or detect back edges directlyDFS depth is not distance from \(s\)

8. Worked DPV exercises

Try each one on paper first. Numbers checked by code.

DPV 3.1 (undirected): DFS, classify edges, pre/post

ABCDEFGHI
DPV 3.1 (undirected).
Answer

Lists: A: B, E · B: A, C, E · C: B, F · D: G, H · E: A, B, F · F: C, E, I · G: D, H · H: D, G · I: F.

A → B → C → F → E. At E, neighbours A and B are on the stack: back edges E–A, E–B. E finishes (5/6). F → I (7/8). Unwind F, C, B, A. Outer loop restarts at D: D → G → H; H–D is a back edge.

ABCDEFGHI1/122/113/1013/185/64/914/1715/167/8
DPV 3.1 after DFS. Labels are pre/post; thick edges are tree edges, dashed edges are non-tree. Dashed = back edges.
ABCDEFGHI
pre/post1/122/113/1013/185/64/914/1715/167/8

Tree edges: A–B, B–C, C–F, F–E, F–I, D–G, G–H. Back edges: E–A, E–B, H–D. Two connected components.

DPV 3.2(a): directed, classify all edges

ABCEDFGH
DPV 3.2(a).
Answer

Out-lists: A: B, F · B: C, E · C: D · D: B, H · E: D, G · F: E, G · G: F · H: G.

A → B → C → D. D → B: B on the stack (back). D → H → G → F → E. E → D and E → G: on the stack (back). E done 8/9. F → G: on the stack (back). F, G, H, D, C done. Back at B: B → E, E finished inside B's interval (forward). Back at A: A → F, finished inside A's interval (forward).

ABCEDFGH1/162/153/148/94/137/106/115/12
DPV 3.2(a) after DFS. Labels are pre/post; thick edges are tree edges, dashed edges are non-tree.
ABCDEFGH
pre/post1/162/153/144/138/97/106/115/12

Tree: A→B, B→C, C→D, D→H, H→G, G→F, F→E. Forward: B→E, A→F. Back: D→B, E→D, E→G, F→G. Cross: none. (Back edges exist, so it has cycles.)

DPV 3.2(b): directed, classify all edges

ABHCGDFE
DPV 3.2(b).
Answer

Out-lists: A: B, H · B: F · C: B · D: C, E · E: none · F: C, D, E · G: A, B, F · H: G.

A → B → F → C. C → B: on the stack (back). C done 4/5. F → D. D → C: C finished before D started (cross). D → E, E done 7/8, D done 6/9. F → E: finished inside F's interval (forward). F 3/10, B 2/11. A → H → G. G → A: on the stack (back). G → B and G → F: finished before G started (cross).

ABHCGDFE1/162/1112/154/513/146/93/107/8
DPV 3.2(b) after DFS. Labels are pre/post; thick edges are tree edges, dashed edges are non-tree.
ABCDEFGH
pre/post1/162/114/56/97/83/1013/1412/15

Tree: A→B, B→F, F→C, F→D, D→E, A→H, H→G. Forward: F→E. Back: C→B, G→A. Cross: D→C, G→B, G→F.

DPV 3.4(i): SCC algorithm

ABECDGHFIJ
DPV 3.4(i): the graph G.

Questions: (a) order SCCs are found, (b) source and sink SCCs, (c) metagraph, (d) minimum edges to add to make it strongly connected. DFS on \(G^R\) is alphabetical.

Answer

Step 1: DFS on \(G^R\). Reverse every edge. Out-lists in \(G^R\) (= in-lists in \(G\)): A: B, E · B: none · C: A, J · D: C · E: none · F: D, H · G: B, H · H: A, I · I: E, G · J: F.

From A: A → B (2/3), A → E (4/5), A done 1/6. From C: C → J → F → D (D → C on stack) D 10/11; F → H → I → G (G's B, H visited) G 14/15, I 13/16, H 12/17, F 9/18, J 8/19, C 7/20.

ABECDGHFIJ1/62/34/57/2010/1114/1512/179/1813/168/19
GR for 3.4(i) with DFS pre/post (alphabetical). Thick = tree edges in GR.
\(G^R\)ABCDEFGHIJ
pre/post1/62/37/2010/114/59/1814/1512/1713/168/19

Decreasing post in \(G^R\): C J F H I G D A E B.

Step 2: explore \(G\) in that order. C reaches D, F, J: {C, D, F, J}. J, F already done. H reaches G, I (F is taken): {G, H, I}. I, G, D done. A: {A}. E: {E}. B: {B}.

BEAGHICDFJ
Metagraph of 3.4(i). Sources B, E; sink CDFJ.

DPV 3.4(ii): SCC algorithm

ABCDEFGHI
DPV 3.4(ii): the graph G.
Answer

Step 1: DFS on \(G^R\). Out-lists in \(G^R\): A: E · B: A · C: B · D: A, G · E: B · F: C, H · G: H · H: D, E, I · I: F, H.

From A: A → E → B (B → A on stack) B 3/4, E 2/5, A 1/6. From C: C → B done, C 7/8. From D: D → G → H → I → F (F's C, H visited) F 13/14, I 12/15, H 11/16, G 10/17, D 9/18.

ABCDEFGHI1/63/47/89/182/513/1410/1711/1612/15
GR for 3.4(ii) with DFS pre/post (alphabetical). Thick = tree edges in GR.
\(G^R\)ABCDEFGHI
pre/post1/63/47/89/182/513/1410/1711/1612/15

Decreasing post in \(G^R\): D G H I F C A E B.

Step 2: explore \(G\). D → H → F → I, H → G: {D,F,G,H,I}. C: only F, taken: {C}. A → B, B → E: {A,B,E}.

ABECDFGHI
Metagraph of 3.4(ii). Source ABE; sink DFGHI.

9. Design problems (HW3 style)

For each: state the graph, the algorithm in one or two lines, why it is correct, and the running time. That is what full credit looks like.

DPV 3.15 Computopia (HW3 P3, also in HW3 solutions)

(a) Intersections = vertices, one-way streets = directed edges. The mayor claims \(G\) is strongly connected. Pick any \(s\). Explore from \(s\) in \(G\) (does \(s\) reach everyone?) and explore from \(s\) in \(G^R\) (does everyone reach \(s\)?). Both reach all vertices ⟺ strongly connected, since any \(u\leadsto s\leadsto v\). Two explores plus building \(G^R\): \(O(|V|+|E|)\). (Or: run the SCC algorithm and check there is exactly one SCC.)

(b) Let \(t\) = town hall. Claim: every vertex reachable from \(t\) can reach \(t\). That means \(t\)'s SCC is a sink SCC: if some edge left \(t\)'s SCC to \(w\), then \(w\) is reachable from \(t\) but cannot get back. Check: compute the SCCs (linear), then check no edge leaves \(t\)'s SCC. Or: \(R\) = explore from \(t\) in \(G\), \(R'\) = explore from \(t\) in \(G^R\), answer yes iff \(R\subseteq R'\). Linear.

DPV 3.16 Minimum semesters (HW3 P4, also in HW3 solutions)

The prerequisite graph is a DAG. The answer is the number of vertices on the longest path. Topologically sort by DFS (decreasing post). Set \(\text{sem}(v) = 1\) for all \(v\); for each \(u\) in topological order and each edge \(u\to w\): \(\text{sem}(w) = \max(\text{sem}(w), \text{sem}(u)+1)\). Output \(\max_v \text{sem}(v)\). Correct because when \(u\) is processed all its prerequisites are already final. \(O(|V|+|E|)\).

DPV 3.22 A vertex that reaches all others

Run DFS on \(G\). Let \(s\) be the vertex with the highest post. Explore from \(s\) again with fresh marks. If it reaches every vertex, answer yes (\(s\)); otherwise no. Why: a vertex that reaches all must be in the unique source SCC, and the highest post always lies in a source SCC. If there are two source SCCs, nobody reaches everything and the test from \(s\) fails, correctly. \(O(|V|+|E|)\).

DPV 3.23 Count paths from s to t in a DAG

Topologically sort. \(\text{paths}(s)=1\), all others 0. For each \(u\) in topological order, for each edge \(u\to v\): \(\text{paths}(v) \mathrel{+}= \text{paths}(u)\). Output \(\text{paths}(t)\). Each path into \(v\) ends with exactly one edge \(u\to v\), and \(u\) is final before \(v\) is used. \(O(|V|+|E|)\). (Use integers; the count can be exponential.)

DPV 3.11 Cycle through a given edge e (undirected)

Let \(e=\{u,v\}\). Remove \(e\), explore from \(u\). There is a cycle through \(e\) iff \(v\) is still reachable (that path plus \(e\) is the cycle). Linear time.

DPV 3.12 Prove or disprove: edge {u,v} and post(u) < post(v) ⟹ v is an ancestor of u

True. Undirected DFS has only tree and back edges, so one endpoint is an ancestor of the other. A descendant finishes before its ancestor. post(u) < post(v) means \(u\) finished first, so \(u\) is the descendant and \(v\) the ancestor.

HW3 P1 counterexamples (also in HW3 solutions)

(a) "Path \(u\leadsto v\) and pre(u) < pre(v) ⟹ \(v\) is a descendant of \(u\)." Edges A→B, B→A, A→C. DFS from A: B 2/3, C 4/5, A 1/6. Path B→A→C exists and pre(B)=2 < pre(C)=4, but C is a sibling of B, not a descendant.

ABC1/62/34/5
HW3 P1(a) counterexample. Labels are pre/post; thick edges are tree edges, dashed edges are non-tree.

(b) "If \(u\) has incoming and outgoing edges, its DFS tree has at least two vertices." Edges B→A, C→B. DFS alphabetical: A alone (1/2), B alone (3/4, its out-neighbour A is finished), C alone (5/6). B has an in-edge and an out-edge but its tree is just {B}.

ABC1/23/45/6
HW3 P1(b) counterexample: three one-vertex DFS trees; both edges are cross edges.
HW3 P2 (stations within 5 links) and HW3 P5 = DPV 3.20 (ancestor labels): one-line ideas

P2. From each station \(s\), run a depth-limited search that stops at depth 5. Plain DFS with visited marks can wrongly block a vertex first reached by a long path, so store the smallest depth seen and re-explore a vertex when it is reached with a smaller depth (or use BFS truncated at depth 5, which gets depths right directly). Collect everything reached.

P5. DFS from the root, keeping the current root-to-node path in an array indexed by depth. At node \(v\) at depth \(d\), its \(k\)th ancestor is path[d − k] (the root if \(d-k \le 0\)), so \(l_{\text{new}}(v) = l(\texttt{path}[\max(0, d-l(v))])\) in \(O(1)\). Read old labels only (write new ones to a separate array). Linear.

10. Self-check

1. Directed DFS gives u = [3, 4] and v = [1, 8]. What kind of edge is u → v?

Back edge: \(\text{pre}(v) < \text{pre}(u) < \text{post}(u) < \text{post}(v)\), so \(v\) is an ancestor of \(u\).

2. Directed DFS gives u = [2, 5] and v = [7, 8]. Can u → v be an edge?

No. \(v\) was unvisited while \(u\) was open, so DFS would have explored \(v\) from \(u\), making it a descendant. (v → u would be a cross edge.)

3. After DFS on a DAG, which vertex is guaranteed to be a sink? A source?

Lowest post is a sink. Highest post is a source.

4. A DAG has edges A→C, B→C, C→D, C→E. How many topological orders?

\(\{A,B\}\) in either order, then C, then \(\{D,E\}\) in either order: \(2\times 2 = 4\).

5. A metagraph has 3 source SCCs and 2 sink SCCs. Minimum edges to add to make the graph strongly connected?

\(\max(3,2) = 3\).

6. Why does the SCC algorithm run DFS on \(G^R\) instead of starting from the highest post in \(G\)?

The highest post in \(G\) is in a source SCC; exploring from there spills into every SCC downstream. The highest post in \(G^R\) is in a source SCC of \(G^R\), which is a sink SCC of \(G\), and exploring \(G\) from a sink SCC finds exactly that SCC.