Algorithm tutorials
Shortest Cycle in a Graph (Girth and Minimum Weight Cycle)
Find the shortest cycle in an unweighted graph with a BFS from every vertex, the minimum-weight cycle of an undirected graph with a Floyd-Warshall twist, and of a directed graph with one Dijkstra per vertex.
This is an extension topic beyond most interviews; it pays off when an OA asks for the length of the shortest cycle or the cheapest loop, a question that plain cycle detection does not answer.
Intuition
Detecting whether a cycle exists is one DFS. Finding the shortest one is harder, because DFS explores in an order unrelated to length. The fix is to combine cycle detection with shortest-path machinery:
- Unweighted, undirected (girth). Run BFS from every vertex
s. Whenever BFS sees an edgeu-vthat is not the tree edge it arrived by andvis already discovered, the two tree paths plus that edge form a closed walk of lengthdist[u] + dist[v] + 1, which contains a cycle at most that long. Ifslies on a shortest cycle, BFS fromsmeets such an edge exactly at the cycle's length. Taking the minimum over all sources is therefore exact. - Weighted, undirected. Floyd-Warshall has a convenient invariant: just before vertex
kbecomes usable as an intermediate,dist[i][j]is the shortesti-jpath through vertices smaller thank. Every cycle has a largest vertexkwith two cycle neighborsiandj, so its weight isdist[i][j] + w(j, k) + w(k, i)at exactly that moment. Check all pairsi < j < kbefore relaxing withk. - Weighted, directed. A cycle through
sis a shortest path fromsto someuplus an edgeu -> s. Run Dijkstra from everys(non-negative weights) and close the loop with each edge enterings.
When to use it in interviews / OAs
- "Return the length of the shortest cycle in the graph" (undirected, unweighted,
nup to about 1000). - "Minimum weight cycle", "cheapest round trip", "smallest loop in a road network".
- "Minimum degree of a connected trio" or triangle problems are the length-3 special case (check common neighbors directly).
- As a contrast: "longest cycle" in a general graph is NP-hard, but if every vertex has at most one outgoing edge (a functional graph) you can walk each chain with timestamps in
O(n).
Template
Girth of an unweighted undirected graph
import heapq
from collections import deque
INF = float("inf")
def shortest_cycle_unweighted(n: int, edges: list[tuple[int, int]]) -> int:
# simple undirected graph (no self-loops or parallel edges); -1 if acyclic
adj: list[list[int]] = [[] for _ in range(n)]
for u, v in edges:
adj[u].append(v)
adj[v].append(u)
best = INF
for s in range(n):
dist = [-1] * n
parent = [-1] * n
dist[s] = 0
q = deque([s])
while q:
u = q.popleft()
for v in adj[u]:
if dist[v] == -1:
dist[v] = dist[u] + 1
parent[v] = u
q.append(v)
elif v != parent[u]: # non-tree edge: closes a cycle of at most this length
best = min(best, dist[u] + dist[v] + 1)
return -1 if best == INF else best
Minimum weight cycle, undirected (Floyd-Warshall)
def min_weight_cycle_undirected(n: int, edges: list[tuple[int, int, int]]) -> float:
# cycles with at least 3 vertices; parallel edges keep their cheapest copy
w = [[INF] * n for _ in range(n)]
for u, v, c in edges:
if u != v:
w[u][v] = w[v][u] = min(w[u][v], c)
dist = [row[:] for row in w]
for i in range(n):
dist[i][i] = 0
best = INF
for k in range(n):
for i in range(k): # cycles whose largest vertex is k: i ... j (all < k), then j-k-i
if w[k][i] == INF:
continue
for j in range(i + 1, k):
cand = dist[i][j] + w[j][k] + w[k][i]
if cand < best:
best = cand
for i in range(n): # now let k be an intermediate vertex
dik = dist[i][k]
if dik == INF:
continue
for j in range(n):
if dik + dist[k][j] < dist[i][j]:
dist[i][j] = dik + dist[k][j]
return best
Minimum weight cycle, directed (Dijkstra from every vertex)
def min_weight_cycle_directed(n: int, edges: list[tuple[int, int, int]]) -> float:
adj: list[list[tuple[int, int]]] = [[] for _ in range(n)]
into: list[list[tuple[int, int]]] = [[] for _ in range(n)] # edges entering each vertex
for u, v, c in edges:
adj[u].append((v, c))
into[v].append((u, c))
best = INF
for s in range(n):
dist = [INF] * n
dist[s] = 0
pq = [(0, s)]
while pq:
d, u = heapq.heappop(pq)
if d > dist[u] or d >= best: # stale, or cannot beat the best cycle
continue
for v, c in adj[u]:
if d + c < dist[v]:
dist[v] = d + c
heapq.heappush(pq, (dist[v], v))
for u, c in into[s]: # close the loop u -> s
best = min(best, dist[u] + c)
return best
Complexity
- BFS from every vertex:
O(n * (n + m))time,O(n + m)memory; fine fornaround 1000 to 2000. - Floyd-Warshall variant:
O(n^3)time,O(n^2)memory; only for smalln(a few hundred in Python). - Dijkstra from every vertex:
O(n * m log n)time; thed >= bestcut-off helps a lot in practice. - Alternative for sparse undirected graphs: for every edge
(u, v, w), remove it and run Dijkstra fromutov; the cycle isdist + w.O(m * m log n).
Common pitfalls
- Counting the tree edge as a cycle. In undirected BFS, the edge back to the parent is not a cycle; with parallel edges you would compare edge ids instead of the parent vertex.
- One BFS is not enough. A single source only finds cycles near it; the girth needs every source (or every edge).
- Checking after relaxing in Floyd. The cycle check for
kmust happen beforekis used as an intermediate, otherwisedist[i][j]may already pass throughkand the "cycle" reuses vertices. - Undirected edges in the directed method. Treating an undirected edge as two directed arcs creates fake 2-cycles
u -> v -> u. - Defining "cycle". Decide whether self-loops (length 1) and 2-cycles count; problems usually exclude them for undirected graphs.
- Negative weights. Dijkstra-based methods require non-negative weights; with negative edges, Floyd-Warshall still works and a negative
dist[i][i]reveals a negative cycle.
Interview variations
- Shortest cycle in a graph. The BFS template;
nup to 1000 is the intended scale. - Minimum weight cycle. Floyd variant for small
n, Dijkstra per vertex or per edge for sparse graphs. - Minimum degree of a connected trio. Enumerate edges
(u, v)and common neighborsw; degree sum minus 6. - Longest cycle where every node has at most one out-edge. Walk from each unvisited node, stamping visit times; when you hit a node stamped in the current walk, the cycle length is the time difference.
- Negative cycle detection. Bellman-Ford's extra round or Floyd's diagonal, covered with shortest paths.