Graphs
Chapter: 3.5 Graphs Level: 3 — Production (from-scratch derivations, code-from-memory, explain-out-loud) Time limit: 45 minutes Total marks: 60
Instructions: Write pseudocode cleanly. State invariants and complexities. Where a trace is asked, show intermediate state, not just final answers.
Question 1 — Dijkstra from memory (12 marks)
(a) Write the priority-queue-based Dijkstra algorithm in pseudocode for a graph with non-negative edge weights, returning shortest distances from source . (6)
(b) State and justify the time complexity when using a binary heap. (2)
(c) Explain out loud (in prose) why Dijkstra fails on graphs with negative edges. Give a concrete 3-vertex counterexample with edge weights and show the wrong answer produced. (4)
Question 2 — Bellman-Ford derivation & negative cycles (10 marks)
(a) State the Bellman-Ford relaxation recurrence and explain why iterations suffice for a graph with vertices (no negative cycle). (4)
(b) Describe precisely how a negative cycle reachable from the source is detected. (3)
(c) Run Bellman-Ford from source on the directed graph: Give final distances . (3)
Question 3 — Topological sort: two algorithms (10 marks)
(a) Write Kahn's algorithm (BFS-based) pseudocode, including how it reports the presence of a cycle. (5)
(b) For the DAG with edges give one valid topological order and state how many distinct valid orders exist. (3)
(c) State the DFS-based method in one sentence and explain the role of finishing times. (2)
Question 4 — MST: Kruskal + Union-Find (12 marks)
(a) Write Kruskal's algorithm using Disjoint Set Union with union by rank + path compression. Include the find and union routines. (7)
(b) State the near-constant amortized complexity of DSU operations and name the function involved. (2)
(c) Compute the MST total weight for the undirected weighted graph: List the edges chosen in Kruskal order and give the total weight. (3)
Question 5 — BFS shortest path & bipartite test (8 marks)
(a) Explain why BFS computes shortest paths in an unweighted graph, referencing the layer/level invariant. (3)
(b) Give pseudocode for a 2-coloring bipartite test built on BFS, reporting false on the first conflict. (5)
Question 6 — Max-flow / Min-cut (8 marks)
(a) State the max-flow min-cut theorem. (2)
(b) Explain why Edmonds-Karp (BFS augmenting paths) guarantees time, i.e. what BFS buys you over generic Ford-Fulkerson. (3)
(c) For the network below (source , sink ), find the max flow value and give a min-cut achieving it: (3)
Answer keyMark scheme & solutions
Question 1
(a) Dijkstra pseudocode (6)
Dijkstra(G, s):
for each v in V: dist[v] = +inf
dist[s] = 0
PQ = min-heap of (0, s)
while PQ not empty:
(d, u) = PQ.extract_min()
if d > dist[u]: continue # stale entry
for each (u,v,w) in adj[u]:
if dist[u] + w < dist[v]:
dist[v] = dist[u] + w
PQ.insert((dist[v], v))
return dist
Marks: init +inf & dist[s]=0 (1); heap use (1); extract-min loop (1); stale-entry skip (1); relaxation condition (1); returns dist (1).
(b) Complexity (2): Each edge triggers at most one insert → inserts, each ; extract-mins each . Total . (1 for expression, 1 for justification.)
(c) Why fails on negatives (4): Dijkstra assumes once a vertex is extracted with minimum tentative distance, that distance is final — greedy finality. A negative edge later can reduce a distance already "finalized", violating the invariant. Counterexample: vertices ; , , . Dijkstra extracts with dist 1 and finalizes it, but true . Wrong answer instead of . (2 for explanation of broken invariant, 2 for valid counterexample + wrong value.)
Question 2
(a) Recurrence & V-1 bound (4): Relaxation: for every edge. A shortest path in a graph with no negative cycle is simple, containing at most edges. After iteration , all shortest paths using edges are correct (proof by induction on edge count). Hence iterations relax all. (2 recurrence, 2 induction argument.)
(b) Negative cycle detection (3): Perform one extra (-th) relaxation pass. If any edge can still be relaxed (), a negative cycle reachable from source exists, since without one all distances would be stable after passes.
(c) Trace (3): From : , , , . Final: . (1 for C, 1 for D, 1 for others.)
Question 3
(a) Kahn's algorithm (5):
Kahn(G):
compute indeg[v] for all v
Q = queue of all v with indeg[v]==0
order = []
while Q not empty:
u = Q.dequeue(); order.append(u)
for each (u,v):
indeg[v] -= 1
if indeg[v]==0: Q.enqueue(v)
if len(order) < V: report CYCLE
else return order
Marks: indegree compute (1); zero-indegree queue init (1); dequeue+append loop (1); decrement/enqueue (1); cycle check by count (1).
(b) Order (3): One valid order: . (Also etc.) Number of distinct valid topological orders = 8. (2 for a valid order, 1 for count.)
(c) DFS method (2): Run DFS; push each vertex onto a stack when it finishes (post-order); reversed finishing order is a topological order. Finishing times ensure a vertex is placed only after all its descendants, so all its out-edges point to earlier-finishing (later-in-stack) vertices.
Question 4
(a) Kruskal + DSU (7):
find(x):
if parent[x] != x: parent[x] = find(parent[x]) # path compression
return parent[x]
union(a,b):
ra=find(a); rb=find(b)
if ra==rb: return false
if rank[ra] < rank[rb]: swap(ra,rb)
parent[rb] = ra
if rank[ra]==rank[rb]: rank[ra]+=1
return true
Kruskal(G):
for each v: parent[v]=v; rank[v]=0
sort edges by weight ascending
mst=[]; total=0
for (u,v,w) in sorted edges:
if union(u,v):
mst.append((u,v,w)); total+=w
return mst, total
Marks: find w/ compression (1.5); union by rank + tie increment (2); init (1); sort (1); accept-edge-if-union (1); accumulate total (0.5).
(b) Complexity (2): Amortized per operation, effectively near-constant; is the inverse Ackermann function.
(c) MST trace (3): Sorted: .
- accept, total=1
- accept, total=3
- accept, total=6 (connects {A,B} to {C,D})
- reject (cycle), reject. Edges: ; total weight = 6. (2 correct edges, 1 total.)
Question 5
(a) BFS shortest path (3): BFS explores vertices in nondecreasing order of distance from source, processing all vertices at level before level . Since every edge has unit weight, the first time a vertex is dequeued its distance equals its BFS level = minimum number of edges from source. FIFO queue preserves this layer ordering.
(b) Bipartite BFS test (5):
isBipartite(G):
color = {} (uncolored)
for each start s with s uncolored:
color[s]=0; Q=[s]
while Q not empty:
u=Q.pop_front()
for each neighbor v of u:
if v uncolored:
color[v] = 1 - color[u]; Q.push(v)
elif color[v]==color[u]:
return false
return true
Marks: outer loop over components (1); color assignment 1-color[u] (1); conflict check same color (1); BFS queue mechanics (1); returns true otherwise (1).
Question 6
(a) Max-flow min-cut theorem (2): The maximum value of an – flow equals the minimum capacity of an – cut.
(b) Edmonds-Karp bound (3): By choosing the shortest augmenting path (fewest edges) via BFS, the BFS distance from to is non-decreasing across augmentations, and each edge becomes "critical" (saturated) times. There are augmentations, each BFS costs , giving . Generic Ford-Fulkerson lacks this bound and may not even terminate with irrational capacities.
(c) Max flow (3): Capacities into : , . Out of : total 5. Push: ; ; remaining has left, route . Total = . Check cut vs rest: . Min-cut capacity 5 = max flow. Max flow = 5, min-cut = edges (capacity 5). (1 flow value, 1 valid routing, 1 min-cut.)
[
{"claim":"Bellman-Ford Q2c distances",
"code":"INF=float('inf')\nedges=[('A','B',4),('A','C',5),('B','C',-6),('C','D',3),('B','D',5)]\nV=['A','B','C','D']\nd={v:INF for v in V}; d['A']=0\nfor _ in range(len(V)-1):\n for u,v,w in edges:\n if d[u]+w<d[v]: d[v]=d[u]+w\nresult = (d['A'],d['B'],d['C'],d['D'])==(0,4,-2,1)"},
{"claim":"Kruskal MST weight Q4c = 6",
"code":"edges=sorted([('A','B',1),('B','C',4),('A','C',3),('C','D',2),('B','D',5)],key=lambda e:e[2])\nparent={v:v for v in 'ABCD'}\ndef find(x):\n while parent[x]!=x: parent[x]=parent[parent[x]]; x=parent[x]\n return x\ntotal=0\nfor u,v,w in edges:\n ru,rv=find(u),find(v)\n if ru!=rv: parent[ru]=rv; total+=w\nresult = total==6"},
{"claim":"Number of topological orders Q3b = 8",
"code":"from itertools import permutations\nE=[(5,2),(5,0),(4,0),(4,1),(2,3),(3,1)]\nV=[0,1,2,3,4,5]\ncnt=0\nfor p in permutations(V):\n pos={v:i for i,v in enumerate(p)}\n if all(pos[a]<pos[b] for a,b in E): cnt+=1\nresult = cnt==8"},
{"claim":"Max flow Q6c = 5 (equals min s-cut)",
"code":"cut = 3+2 # s->a + s->b\ninto_t = 2+3\nresult = min(cut,5)==5 and cut==5"}
]